From eecf7977e2ed59901ee5e6917e0b85cd4b71ff90 Mon Sep 17 00:00:00 2001 From: Yuriy Artamonov Date: Wed, 5 Feb 2025 19:10:56 +0100 Subject: [PATCH] [platform] IJPL-176930 Move intellij.platform.ide.provisioner to the platform (cherry picked from commit de2b62ede7c49f30046f519d97a76f769ce42702) IJ-MR-154430 GitOrigin-RevId: 9c1b221162411c9251c1feff666328b00c1385ec --- .../ProvisionedServiceConfiguration.kt | 37 ++++++++++++++++ .../ProvisionedServiceDescriptor.kt | 19 ++++++++ .../provisioner/ProvisionedServiceRegistry.kt | 23 ++++++++++ .../ProvisionerCompanyBrandingProvider.kt | 39 ++++++++++++++++ .../ide/provisioner/endpoint/AuthToken.kt | 31 +++++++++++++ .../provisioner/endpoint/ServiceEndpoint.kt | 44 +++++++++++++++++++ .../provisioner/endpoint/package-info.java | 5 +++ .../ide/provisioner/package-info.java | 5 +++ .../src/META-INF/PlatformExtensions.xml | 4 ++ 9 files changed, 207 insertions(+) create mode 100644 platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceConfiguration.kt create mode 100644 platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceDescriptor.kt create mode 100644 platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceRegistry.kt create mode 100644 platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionerCompanyBrandingProvider.kt create mode 100644 platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/AuthToken.kt create mode 100644 platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/ServiceEndpoint.kt create mode 100644 platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/package-info.java create mode 100644 platform/platform-impl/src/com/intellij/platform/ide/provisioner/package-info.java diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceConfiguration.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceConfiguration.kt new file mode 100644 index 000000000000..710d6ce7a723 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceConfiguration.kt @@ -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, + + /** 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 + } +} diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceDescriptor.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceDescriptor.kt new file mode 100644 index 000000000000..f2fbc1917241 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceDescriptor.kt @@ -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 +} diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceRegistry.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceRegistry.kt new file mode 100644 index 000000000000..ca028d6520c8 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceRegistry.kt @@ -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 +} diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionerCompanyBrandingProvider.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionerCompanyBrandingProvider.kt new file mode 100644 index 000000000000..2efa61184f91 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionerCompanyBrandingProvider.kt @@ -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 + + @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, +) diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/AuthToken.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/AuthToken.kt new file mode 100644 index 000000000000..36bb5cfa927a --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/AuthToken.kt @@ -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, +) { + @Deprecated("For backward compatibility, until TBE plugin is updated") + @Suppress("unused") + constructor( + tokenValue: String, + tokenSchema: String, + additionalHeaders: Map, + ) : 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 + } +} diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/ServiceEndpoint.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/ServiceEndpoint.kt new file mode 100644 index 000000000000..9745ad6daab1 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/ServiceEndpoint.kt @@ -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 + + /** + * 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) {} +} diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/package-info.java b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/package-info.java new file mode 100644 index 000000000000..287d7f7455b7 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/package-info.java @@ -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; diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/package-info.java b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/package-info.java new file mode 100644 index 000000000000..f4a6d9320150 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/package-info.java @@ -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; diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 760f637e38ee..088969aa559c 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -1864,6 +1864,10 @@ + +