Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repositories {
}

dependencies {
implementation 'org.openapitools:openapi-generator-gradle-plugin:5.4.0'
implementation 'org.openapitools:openapi-generator-gradle-plugin:6.6.0'
implementation 'de.undercouch:gradle-download-task:5.0.2'
implementation 'com.github.ben-manes:gradle-versions-plugin:0.42.0'
}
3 changes: 2 additions & 1 deletion symphony-bdk-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,14 @@ dependencies {
}

// OpenAPI code generation
def apiBaseUrl = "https://raw.githubusercontent.com/finos/symphony-api-spec/fc80c3204d8a92a0b82d3c951eab7f5cb78a7c53"
def apiBaseUrl = "https://raw.githubusercontent.com/tzhao-symphony/symphony-api-spec/refs/heads/ADMIN-10454/add_app_users_endpoint/"
def generatedFolder = "$buildDir/generated/openapi"
def apisToGenerate = [
Agent: 'agent/agent-api-public-deprecated.yaml',
Pod : 'pod/pod-api-public-deprecated.yaml',
Auth : 'authenticator/authenticator-api-public-deprecated.yaml',
Login: 'login/login-api-public.yaml',
Users: 'users/users-api-public.yaml'
]

sourceSets.main.java.srcDirs += "$generatedFolder/src/main/java"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.symphony.bdk.core;

import com.symphony.bdk.core.auth.ExtAppAuthSession;
import com.symphony.bdk.core.client.ApiClientFactory;
import com.symphony.bdk.core.config.model.BdkConfig;
import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder;
import com.symphony.bdk.core.service.app.AppUsersService;
import com.symphony.bdk.gen.api.AppsApi;
import com.symphony.bdk.http.api.ApiClient;

import lombok.extern.slf4j.Slf4j;
import org.apiguardian.api.API;

/**
* Factory responsible for creating Ext App service instances for Symphony Bdk apps entry point:
* <ul>
* <li>{@link AppUsersService}</li>
* </ul>
*/
@Slf4j
@API(status = API.Status.INTERNAL)
class ExtAppServiceFactory {

private final ApiClient usersClient;
private final ExtAppAuthSession authSession;
private final BdkConfig config;
private final RetryWithRecoveryBuilder<?> retryBuilder;

public ExtAppServiceFactory(ApiClientFactory apiClientFactory, ExtAppAuthSession authSession, BdkConfig config) {
this.config = config;
this.usersClient = apiClientFactory.getUsersClient();
this.authSession = authSession;
this.retryBuilder = new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry());


}

/**
* Returns a fully initialized {@link AppUsersService}.
*
* @return a new {@link AppUsersService} instance.
*/
public AppUsersService getAppService() {
return new AppUsersService(config.getApp().getAppId(), new AppsApi(usersClient), this.authSession, this.retryBuilder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.symphony.bdk.core;

import com.symphony.bdk.core.auth.ExtAppAuthSession;
import com.symphony.bdk.core.client.ApiClientFactory;
import com.symphony.bdk.core.config.model.BdkConfig;
import com.symphony.bdk.core.service.app.AppUsersService;

import org.apiguardian.api.API;

/**
* Entry point for external application services relying on the App session token
*/
@API(status = API.Status.STABLE)
public class ExtAppServices {
AppUsersService appUsersService;

public ExtAppServices(ApiClientFactory apiClientFactory, ExtAppAuthSession authSession, BdkConfig config) {
ExtAppServiceFactory extAppServiceFactory = new ExtAppServiceFactory(apiClientFactory, authSession, config);
this.appUsersService = extAppServiceFactory.getAppService();
}

/**
* Get the {@link AppUsersService}.
*
* @return an {@link AppUsersService} instance.
*/
public AppUsersService appUsers() {
return this.appUsersService;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class SymphonyBdk {
private final ExtensionAppAuthenticator extensionAppAuthenticator;

private final AuthSession botSession;
private final ExtAppAuthSession extAppAuthSession;
private final UserV2 botInfo;
private final DatafeedLoop datafeedLoop;
private final DatahoseLoop datahoseLoop;
Expand All @@ -64,6 +65,9 @@ public class SymphonyBdk {
private final HealthService healthService;
private final ExtensionService extensionService;

private final ExtAppServices extAppServices;


/**
* Returns a new {@link SymphonyBdkBuilder} for fluent initialization.
*
Expand Down Expand Up @@ -128,6 +132,15 @@ protected SymphonyBdk(
this.messageService = serviceFactory != null ? serviceFactory.getMessageService() : null;
this.disclaimerService = serviceFactory != null ? serviceFactory.getDisclaimerService() : null;

if (config.isOboConfigured()) {
ExtAppAuthenticator extAppAuthenticator = authenticatorFactory.getExtAppAuthenticator();
this.extAppAuthSession = extAppAuthenticator.authenticateExtApp();
this.extAppServices = new ExtAppServices(apiClientFactory, this.extAppAuthSession, this.config);
} else {
this.extAppServices = null;
this.extAppAuthSession = null;
}

// retrieve bot session info
this.botInfo = sessionService != null ? sessionService.getSession() : null;

Expand Down Expand Up @@ -304,6 +317,14 @@ public OboServices obo(AuthSession oboSession) {
return new OboServices(config, oboSession);
}

/**
* Get an {@link ExtAppServices} gathering all extension app enabled services
* @return an {@link ExtAppServices} instance
*/
public ExtAppServices app() {
return this.extAppServices;
}

/**
* Returns the {@link ExtensionAppAuthenticator}.
*
Expand All @@ -313,6 +334,17 @@ public ExtensionAppAuthenticator appAuthenticator() {
return this.getExtensionAppAuthenticator();
}

/**
* Returns the extension app auth session.
*
* @return extension app auth session.
*/
@API(status = API.Status.EXPERIMENTAL)
public ExtAppAuthSession extAppAuthSession() {
return Optional.ofNullable(this.extAppAuthSession)
.orElseThrow(() -> new IllegalStateException("Cannot get App auth session. Ext app is not configured."));
}

/**
* Returns the Bot session.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,13 @@ public interface AuthenticatorFactory {
*/
@Nonnull
ExtensionAppAuthenticator getExtensionAppAuthenticator() throws AuthInitializationException;

/**
* Creates a new instance of a {@link ExtAppAuthenticator}.
*
* @return a new {@link ExtAppAuthenticator} instance.
* @throws AuthInitializationException if the authenticator cannot be instantiated.
*/
@Nonnull
ExtAppAuthenticator getExtAppAuthenticator() throws AuthInitializationException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.symphony.bdk.core.auth;

import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;

import org.apiguardian.api.API;

import javax.annotation.Nullable;

/**
* Extension App Authentication session handle. The {@link ExtAppAuthSession#refresh()} will trigger a re-auth against the API endpoints.
*/
@API(status = API.Status.STABLE)
public interface ExtAppAuthSession {
/**
* Extension app session token.
*
* @return extension app session token
*/
@Nullable
String getAppSession();

/**
* Trigger re-authentication to refresh session token.
*/
void refresh() throws AuthUnauthorizedException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.symphony.bdk.core.auth;

import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;
import org.apiguardian.api.API;

import javax.annotation.Nonnull;

/**
* Extension App authenticator service.
*/
@API(status = API.Status.STABLE)
public interface ExtAppAuthenticator {

/**
* Authenticates an extension app.
*
* @return the authentication session.
*/
@Nonnull ExtAppAuthSession authenticateExtApp() throws AuthUnauthorizedException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.symphony.bdk.core.auth.impl;

import com.symphony.bdk.core.auth.ExtAppAuthenticator;
import com.symphony.bdk.core.auth.OboAuthenticator;
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;
import com.symphony.bdk.core.config.model.BdkRetryConfig;
import com.symphony.bdk.http.api.ApiException;

import lombok.extern.slf4j.Slf4j;
import org.apiguardian.api.API;

/**
* Abstract class to factorize the {@link OboAuthenticator} logic between RSA and certificate,
* especially the retry logic on top of HTTP calls.
*/
@Slf4j
@API(status = API.Status.INTERNAL)
public abstract class AbstractExtAppAuthenticator implements ExtAppAuthenticator {

protected final String appId;
private final AuthenticationRetry<String> authenticationRetry;

protected AbstractExtAppAuthenticator(BdkRetryConfig retryConfig, String appId) {
this.appId = appId;
this.authenticationRetry = new AuthenticationRetry<>(retryConfig);
}

protected String retrieveAppSessionToken() throws AuthUnauthorizedException {
log.debug("Start authenticating app with id : {} ...", appId);

final String unauthorizedErrorMessage = "Unable to authenticate app with ID : " + appId + ". "
+ "It usually happens when the app has not been configured or is not activated.";

return authenticationRetry.executeAndRetry("AbstractExtAppAuthenticator.retrieveAppSessionToken", getBasePath(),
this::authenticateAndRetrieveAppSessionToken, unauthorizedErrorMessage);
}

protected abstract String authenticateAndRetrieveAppSessionToken() throws ApiException;

protected abstract String getBasePath();
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,43 @@ ExtensionAppAuthenticator getExtensionAppAuthenticator() throws AuthInitializati
throw new AuthInitializationException("Neither RSA private key nor certificate is configured.");
}

/**
* Creates a new instance of a {@link ExtAppAuthenticator} service.
*
* @return a new {@link ExtAppAuthenticator} instance.
*/
@Nonnull
@Override
public ExtAppAuthenticator getExtAppAuthenticator() throws AuthInitializationException {
if (this.config.getApp().isBothCertificateAndRsaConfigured()) {
throw new AuthInitializationException(
"Both of certificate and rsa authentication are configured. Only one of them should be provided.");
}
if (this.config.getApp().isCertificateAuthenticationConfigured()) {
if (!this.config.getApp().isCertificateConfigurationValid()) {
throw new AuthInitializationException(
"Only one of certificate path or content should be configured for app authentication.");
}
return new ExtAppAuthenticatorCertImpl(
this.config.getRetry(),
this.config.getApp().getAppId(),
this.apiClientFactory.getExtAppSessionAuthClient());
}
if (this.config.getApp().isRsaAuthenticationConfigured()) {
if (!this.config.getApp().isRsaConfigurationValid()) {
throw new AuthInitializationException(
"Only one of private key path or content should be configured for app authentication.");
}
return new ExtAppAuthenticatorRsaImpl(
this.config.getRetry(),
this.config.getApp().getAppId(),
this.loadPrivateKeyFromAuthenticationConfig(this.config.getApp()),
this.apiClientFactory.getLoginClient()
);
}
throw new AuthInitializationException("Neither RSA private key nor certificate is configured.");
}

private PrivateKey loadPrivateKeyFromAuthenticationConfig(BdkAuthenticationConfig config)
throws AuthInitializationException {
String privateKeyPath = "";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.symphony.bdk.core.auth.impl;

import com.symphony.bdk.core.auth.ExtAppAuthSession;
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;

import org.apiguardian.api.API;
import org.jetbrains.annotations.Nullable;

/**
* {@link ExtAppAuthSession} impl for Extension App Certificate authentication mode.
*/
@API(status = API.Status.INTERNAL)
public class ExtAppAuthSessionCertImpl implements ExtAppAuthSession {

String appSession;
ExtAppAuthenticatorCertImpl authenticator;

public ExtAppAuthSessionCertImpl(ExtAppAuthenticatorCertImpl authenticator) {
this.authenticator = authenticator;
}

@Nullable
@Override
public String getAppSession() {
return appSession;
}

@Override
public void refresh() throws AuthUnauthorizedException {
this.appSession = this.authenticator.retrieveAppSessionToken();
}

/**
* This method is only visible for testing.
*/
protected ExtAppAuthenticatorCertImpl getAuthenticator() {
return authenticator;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.symphony.bdk.core.auth.impl;

import com.symphony.bdk.core.auth.ExtAppAuthSession;
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;

import org.apiguardian.api.API;
import org.jetbrains.annotations.Nullable;

/**
* {@link ExtAppAuthSession} impl for Extension App RSA authentication mode.
*/
@API(status = API.Status.INTERNAL)
public class ExtAppAuthSessionImpl implements ExtAppAuthSession {
ExtAppAuthenticatorRsaImpl authenticator;
String appSession;

public ExtAppAuthSessionImpl(ExtAppAuthenticatorRsaImpl authenticator) {
this.authenticator = authenticator;
}

@Nullable
@Override
public String getAppSession() {
return appSession;
}

@Override
public void refresh() throws AuthUnauthorizedException {
this.appSession = this.authenticator.retrieveAppSessionToken();
}

/**
* This method is only visible for testing.
*/
protected ExtAppAuthenticatorRsaImpl getAuthenticator() {
return authenticator;
}
}
Loading
Loading