[platform] IJPL-176930 Move intellij.platform.ide.provisioner to the platform

(cherry picked from commit de2b62ede7c49f30046f519d97a76f769ce42702)

IJ-MR-154430

GitOrigin-RevId: 9c1b221162411c9251c1feff666328b00c1385ec
This commit is contained in:
Yuriy Artamonov
2025-02-05 19:10:56 +01:00
committed by intellij-monorepo-bot
parent e68cf608d9
commit eecf7977e2
9 changed files with 207 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
package com.intellij.platform.ide.provisioner
import com.intellij.platform.ide.provisioner.endpoint.ServiceEndpoint
data class ProvisionedServiceConfiguration(
/** Generic Key-Value map of service-specific properties. */
private val properties: Map<String, String>,
/** Endpoint descriptor in case the service involves a remote server. */
val endpoint: ServiceEndpoint?,
) {
operator fun get(key: String): String? = properties[key]
}
sealed interface ProvisionedServiceConfigurationResult {
/**
* Represents the successfully loaded state.
*/
sealed interface Success : ProvisionedServiceConfigurationResult {
data class ServiceProvisioned(val configuration: ProvisionedServiceConfiguration) : Success
data object ServiceNotProvisioned : Success
}
/**
* Denotes that the provisioner could not load the configuration of the service due to an error.
* Depending on the particular service, there may be different ways of treating this state.
* For example, the client may fall back to some predefined default configuration,
* or it may choose to prohibit the use of the corresponding IDE functionality altogether
* until a proper configuration becomes available.
*/
sealed interface Failure : ProvisionedServiceConfigurationResult {
val message: String
data class LoginRequired(override val message: String) : Failure
data class GenericError(override val message: String, val cause: Throwable? = null) : Failure
}
}

View File

@@ -0,0 +1,19 @@
package com.intellij.platform.ide.provisioner
import kotlinx.coroutines.flow.Flow
/**
* Descriptor for a (potentially provisioned) piece of IDE functionality.
*/
interface ProvisionedServiceDescriptor {
/** Unique identifier for accessing the service endpoint using [ProvisionedServiceRegistry.getServiceById]. */
val id: String
/**
* The state of whether the service is provisioned (with its configuration in that case) or not.
*
* Note that it may take some time for the initial value to become available in the flow
* while the configuration is still loading.
*/
val configurationFlow: Flow<ProvisionedServiceConfigurationResult>
}

View File

@@ -0,0 +1,23 @@
package com.intellij.platform.ide.provisioner
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
interface ProvisionedServiceRegistry {
/**
* Retrieves a [ProvisionedServiceDescriptor] by its [ProvisionedServiceDescriptor.id],
* or null if the provisioner doesn't recognize the ID.
* Note that a non-null result only means that the installed version of the provisioner plugin
* is aware and support the requested service; it doesn't mean that the service is available and/or enabled -
* this is what [ProvisionedServiceDescriptor.configurationFlow] is for.
*/
fun getServiceById(id: String): ProvisionedServiceDescriptor?
companion object {
fun getInstance(): ProvisionedServiceRegistry = ApplicationManager.getApplication().service()
}
}
internal class DefaultProvisionedServiceRegistry : ProvisionedServiceRegistry {
override fun getServiceById(id: String): ProvisionedServiceDescriptor? = null
}

View File

@@ -0,0 +1,39 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.ide.provisioner
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.util.NlsSafe
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import org.jetbrains.annotations.ApiStatus
import javax.swing.Icon
@ApiStatus.Internal
interface ProvisionerCompanyBrandingProvider{
val companyBranding: Flow<CompanyBranding>
@ApiStatus.Experimental
fun getCurrentEnterpriseState(): CompanyBranding
companion object {
fun getInstance(): ProvisionerCompanyBrandingProvider = ApplicationManager.getApplication().service()
}
}
internal class DefaultProvisionerCompanyBrandingProvider: ProvisionerCompanyBrandingProvider {
override val companyBranding = flowOf(CompanyBranding.NotProvisioned)
override fun getCurrentEnterpriseState(): CompanyBranding = CompanyBranding.NotProvisioned
}
sealed class CompanyBranding {
data object NotReady: CompanyBranding()
data class Provisioned(val info: EnterpriseInfo): CompanyBranding()
data object NotProvisioned: CompanyBranding()
}
data class EnterpriseInfo(
val logo: Icon,
val companyName: @NlsSafe String,
val browserUrl: String,
)

View File

@@ -0,0 +1,31 @@
package com.intellij.platform.ide.provisioner.endpoint
data class AuthToken(
/**
* The map of HTTP request headers required for authenticating with the corresponding [ServiceEndpoint].
* Typically, it contains at least the `"Authorization"` credentials, but that's not guaranteed.
*/
val requestHeaders: Map<String, String>,
) {
@Deprecated("For backward compatibility, until TBE plugin is updated")
@Suppress("unused")
constructor(
tokenValue: String,
tokenSchema: String,
additionalHeaders: Map<String, String>,
) : this(requestHeaders = mapOf("Authorization" to "${tokenSchema} ${tokenValue}") + additionalHeaders)
}
sealed interface AuthTokenResult {
data class Success(val token: AuthToken) : AuthTokenResult
sealed interface Failure : AuthTokenResult {
val message: String
data class Timeout(override val message: String) : Failure
data class NetworkError(override val message: String) : Failure
data class LoginRequired(override val message: String) : Failure
data class ValidationError(override val message: String) : Failure
data class GenericError(override val message: String, val cause: Throwable? = null) : Failure
}
}

View File

@@ -0,0 +1,44 @@
package com.intellij.platform.ide.provisioner.endpoint
import kotlinx.coroutines.flow.Flow
/**
* Describes a server endpoint for the provisioned service.
*/
interface ServiceEndpoint {
/** The URL of the service endpoint. */
val serverUrl: String
/**
* The current [token][AuthTokenResult] required to access the [server][serverUrl].
* The implementation is responsible for refreshing the token, so that the latest token value
* available in the flow is always usable (unless there's an [AuthTokenResult.Failure]).
*
* Note that an [AuthTokenResult.Success] doesn't guarantee that the token is going to stay
* valid up until its expiration time.
* The client code should still be able to handle an authorization error properly,
* and the [reportAuthFailure] method can be helpful in facilitating that.
*/
val authTokenFlow: Flow<AuthTokenResult>
/**
* The client is advised to call this method as part of graceful error handling if a request
* to the endpoint fails because of an authentication failure ("401 Unauthorized").
*
* The token may become invalid due to an external change; for instance, the SSO provider
* may forcibly log the user out. An event like that may go unnoticed by the provisioner,
* and the new state may not be reflected in the [authTokenFlow] automatically.
*
* By calling this method, the client notifies the provisioner that the token is no more
* usable. The provisioner then makes the best effort to revalidate the token, which may
* hopefully result in a new token state (probably an [AuthTokenResult.Failure.LoginRequired])
* being pushed eventually through the [authTokenFlow].
*
* Note that the contract of this method should indeed be only treated as "best effort".
* The client must not rely on a new token state becoming available in the [authTokenFlow]
* right after calling this method, and should design the interaction with the user accordingly.
* In this sense, the default "noop" way of how the provisioner could implement this method
* is perfectly fine.
*/
fun reportAuthFailure(authToken: AuthToken) {}
}

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Internal
package com.intellij.platform.ide.provisioner.endpoint;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Internal
package com.intellij.platform.ide.provisioner;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -1864,6 +1864,10 @@
<applicationService serviceInterface="com.intellij.auth.GenericAuthService"
serviceImplementation="com.intellij.auth.LocalGenericAuthService"
client="local"/>
<applicationService serviceInterface="com.intellij.platform.ide.provisioner.ProvisionedServiceRegistry"
serviceImplementation="com.intellij.platform.ide.provisioner.DefaultProvisionedServiceRegistry"/>
<applicationService serviceInterface="com.intellij.platform.ide.provisioner.ProvisionerCompanyBrandingProvider"
serviceImplementation="com.intellij.platform.ide.provisioner.DefaultProvisionerCompanyBrandingProvider"/>
</extensions>
<applicationListeners>