Skip to content

Commit ccddbce

Browse files
UPL-257 Add bdk.app().appUsers().listAppUsers()
1 parent 0986508 commit ccddbce

30 files changed

Lines changed: 1237 additions & 14 deletions

buildSrc/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ repositories {
77
}
88

99
dependencies {
10-
implementation 'org.openapitools:openapi-generator-gradle-plugin:5.4.0'
10+
implementation 'org.openapitools:openapi-generator-gradle-plugin:6.6.0'
1111
implementation 'de.undercouch:gradle-download-task:5.0.2'
1212
implementation 'com.github.ben-manes:gradle-versions-plugin:0.42.0'
1313
}

symphony-bdk-core/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,14 @@ dependencies {
7676
}
7777

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

8889
sourceSets.main.java.srcDirs += "$generatedFolder/src/main/java"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.symphony.bdk.core;
2+
3+
import com.symphony.bdk.core.auth.ExtAppAuthSession;
4+
import com.symphony.bdk.core.client.ApiClientFactory;
5+
import com.symphony.bdk.core.config.model.BdkConfig;
6+
import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder;
7+
import com.symphony.bdk.core.service.app.AppUsersService;
8+
import com.symphony.bdk.gen.api.AppsApi;
9+
import com.symphony.bdk.http.api.ApiClient;
10+
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.apiguardian.api.API;
13+
14+
/**
15+
* Factory responsible for creating Ext App service instances for Symphony Bdk apps entry point:
16+
* <ul>
17+
* <li>{@link AppUsersService}</li>
18+
* </ul>
19+
*/
20+
@Slf4j
21+
@API(status = API.Status.INTERNAL)
22+
class ExtAppServiceFactory {
23+
24+
private final ApiClient usersClient;
25+
private final ExtAppAuthSession authSession;
26+
private final BdkConfig config;
27+
private final RetryWithRecoveryBuilder<?> retryBuilder;
28+
29+
public ExtAppServiceFactory(ApiClientFactory apiClientFactory, ExtAppAuthSession authSession, BdkConfig config) {
30+
this.config = config;
31+
this.usersClient = apiClientFactory.getUsersClient();
32+
this.authSession = authSession;
33+
this.retryBuilder = new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry());
34+
35+
36+
}
37+
38+
/**
39+
* Returns a fully initialized {@link AppUsersService}.
40+
*
41+
* @return a new {@link AppUsersService} instance.
42+
*/
43+
public AppUsersService getAppService() {
44+
return new AppUsersService(config.getApp().getAppId(), new AppsApi(usersClient), this.authSession, this.retryBuilder);
45+
}
46+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.symphony.bdk.core;
2+
3+
import com.symphony.bdk.core.auth.ExtAppAuthSession;
4+
import com.symphony.bdk.core.client.ApiClientFactory;
5+
import com.symphony.bdk.core.config.model.BdkConfig;
6+
import com.symphony.bdk.core.service.app.AppUsersService;
7+
8+
import org.apiguardian.api.API;
9+
10+
/**
11+
* Entry point for external application services relying on the App session token
12+
*/
13+
@API(status = API.Status.STABLE)
14+
public class ExtAppServices {
15+
AppUsersService appUsersService;
16+
17+
public ExtAppServices(ApiClientFactory apiClientFactory, ExtAppAuthSession authSession, BdkConfig config) {
18+
ExtAppServiceFactory extAppServiceFactory = new ExtAppServiceFactory(apiClientFactory, authSession, config);
19+
this.appUsersService = extAppServiceFactory.getAppService();
20+
}
21+
22+
/**
23+
* Get the {@link AppUsersService}.
24+
*
25+
* @return an {@link AppUsersService} instance.
26+
*/
27+
public AppUsersService appUsers() {
28+
return this.appUsersService;
29+
};
30+
}

symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdk.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class SymphonyBdk {
4848
private final ExtensionAppAuthenticator extensionAppAuthenticator;
4949

5050
private final AuthSession botSession;
51+
private final ExtAppAuthSession extAppAuthSession;
5152
private final UserV2 botInfo;
5253
private final DatafeedLoop datafeedLoop;
5354
private final DatahoseLoop datahoseLoop;
@@ -64,6 +65,9 @@ public class SymphonyBdk {
6465
private final HealthService healthService;
6566
private final ExtensionService extensionService;
6667

68+
private final ExtAppServices extAppServices;
69+
70+
6771
/**
6872
* Returns a new {@link SymphonyBdkBuilder} for fluent initialization.
6973
*
@@ -128,6 +132,15 @@ protected SymphonyBdk(
128132
this.messageService = serviceFactory != null ? serviceFactory.getMessageService() : null;
129133
this.disclaimerService = serviceFactory != null ? serviceFactory.getDisclaimerService() : null;
130134

135+
if (config.isOboConfigured()) {
136+
ExtAppAuthenticator extAppAuthenticator = authenticatorFactory.getExtAppAuthenticator();
137+
this.extAppAuthSession = extAppAuthenticator.authenticateExtApp();
138+
this.extAppServices = new ExtAppServices(apiClientFactory, this.extAppAuthSession, this.config);
139+
} else {
140+
this.extAppServices = null;
141+
this.extAppAuthSession = null;
142+
}
143+
131144
// retrieve bot session info
132145
this.botInfo = sessionService != null ? sessionService.getSession() : null;
133146

@@ -304,6 +317,14 @@ public OboServices obo(AuthSession oboSession) {
304317
return new OboServices(config, oboSession);
305318
}
306319

320+
/**
321+
* Get an {@link ExtAppServices} gathering all extension app enabled services
322+
* @return an {@link ExtAppServices} instance
323+
*/
324+
public ExtAppServices app() {
325+
return this.extAppServices;
326+
}
327+
307328
/**
308329
* Returns the {@link ExtensionAppAuthenticator}.
309330
*
@@ -313,6 +334,17 @@ public ExtensionAppAuthenticator appAuthenticator() {
313334
return this.getExtensionAppAuthenticator();
314335
}
315336

337+
/**
338+
* Returns the extension app auth session.
339+
*
340+
* @return extension app auth session.
341+
*/
342+
@API(status = API.Status.EXPERIMENTAL)
343+
public ExtAppAuthSession extAppAuthSession() {
344+
return Optional.ofNullable(this.extAppAuthSession)
345+
.orElseThrow(() -> new IllegalStateException("Cannot get App auth session. Ext app is not configured."));
346+
}
347+
316348
/**
317349
* Returns the Bot session.
318350
*

symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/AuthenticatorFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,13 @@ public interface AuthenticatorFactory {
4343
*/
4444
@Nonnull
4545
ExtensionAppAuthenticator getExtensionAppAuthenticator() throws AuthInitializationException;
46+
47+
/**
48+
* Creates a new instance of a {@link ExtAppAuthenticator}.
49+
*
50+
* @return a new {@link ExtAppAuthenticator} instance.
51+
* @throws AuthInitializationException if the authenticator cannot be instantiated.
52+
*/
53+
@Nonnull
54+
ExtAppAuthenticator getExtAppAuthenticator() throws AuthInitializationException;
4655
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.symphony.bdk.core.auth;
2+
3+
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;
4+
5+
import org.apiguardian.api.API;
6+
7+
import javax.annotation.Nullable;
8+
9+
/**
10+
* Extension App Authentication session handle. The {@link ExtAppAuthSession#refresh()} will trigger a re-auth against the API endpoints.
11+
*/
12+
@API(status = API.Status.STABLE)
13+
public interface ExtAppAuthSession {
14+
/**
15+
* Extension app session token.
16+
*
17+
* @return extension app session token
18+
*/
19+
@Nullable
20+
String getAppSession();
21+
22+
/**
23+
* Trigger re-authentication to refresh session token.
24+
*/
25+
void refresh() throws AuthUnauthorizedException;
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.symphony.bdk.core.auth;
2+
3+
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;
4+
import org.apiguardian.api.API;
5+
6+
import javax.annotation.Nonnull;
7+
8+
/**
9+
* Extension App authenticator service.
10+
*/
11+
@API(status = API.Status.STABLE)
12+
public interface ExtAppAuthenticator {
13+
14+
/**
15+
* Authenticates an extension app.
16+
*
17+
* @return the authentication session.
18+
*/
19+
@Nonnull ExtAppAuthSession authenticateExtApp() throws AuthUnauthorizedException;
20+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.symphony.bdk.core.auth.impl;
2+
3+
import com.symphony.bdk.core.auth.ExtAppAuthenticator;
4+
import com.symphony.bdk.core.auth.OboAuthenticator;
5+
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;
6+
import com.symphony.bdk.core.config.model.BdkRetryConfig;
7+
import com.symphony.bdk.http.api.ApiException;
8+
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.apiguardian.api.API;
11+
12+
/**
13+
* Abstract class to factorize the {@link OboAuthenticator} logic between RSA and certificate,
14+
* especially the retry logic on top of HTTP calls.
15+
*/
16+
@Slf4j
17+
@API(status = API.Status.INTERNAL)
18+
public abstract class AbstractExtAppAuthenticator implements ExtAppAuthenticator {
19+
20+
protected final String appId;
21+
private final AuthenticationRetry<String> authenticationRetry;
22+
23+
protected AbstractExtAppAuthenticator(BdkRetryConfig retryConfig, String appId) {
24+
this.appId = appId;
25+
this.authenticationRetry = new AuthenticationRetry<>(retryConfig);
26+
}
27+
28+
protected String retrieveAppSessionToken() throws AuthUnauthorizedException {
29+
log.debug("Start authenticating app with id : {} ...", appId);
30+
31+
final String unauthorizedErrorMessage = "Unable to authenticate app with ID : " + appId + ". "
32+
+ "It usually happens when the app has not been configured or is not activated.";
33+
34+
return authenticationRetry.executeAndRetry("AbstractExtAppAuthenticator.retrieveAppSessionToken", getBasePath(),
35+
this::authenticateAndRetrieveAppSessionToken, unauthorizedErrorMessage);
36+
}
37+
38+
protected abstract String authenticateAndRetrieveAppSessionToken() throws ApiException;
39+
40+
protected abstract String getBasePath();
41+
}

symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/AuthenticatorFactoryImpl.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,43 @@ ExtensionAppAuthenticator getExtensionAppAuthenticator() throws AuthInitializati
179179
throw new AuthInitializationException("Neither RSA private key nor certificate is configured.");
180180
}
181181

182+
/**
183+
* Creates a new instance of a {@link ExtAppAuthenticator} service.
184+
*
185+
* @return a new {@link ExtAppAuthenticator} instance.
186+
*/
187+
@Nonnull
188+
@Override
189+
public ExtAppAuthenticator getExtAppAuthenticator() throws AuthInitializationException {
190+
if (this.config.getApp().isBothCertificateAndRsaConfigured()) {
191+
throw new AuthInitializationException(
192+
"Both of certificate and rsa authentication are configured. Only one of them should be provided.");
193+
}
194+
if (this.config.getApp().isCertificateAuthenticationConfigured()) {
195+
if (!this.config.getApp().isCertificateConfigurationValid()) {
196+
throw new AuthInitializationException(
197+
"Only one of certificate path or content should be configured for app authentication.");
198+
}
199+
return new ExtAppAuthenticatorCertImpl(
200+
this.config.getRetry(),
201+
this.config.getApp().getAppId(),
202+
this.apiClientFactory.getExtAppSessionAuthClient());
203+
}
204+
if (this.config.getApp().isRsaAuthenticationConfigured()) {
205+
if (!this.config.getApp().isRsaConfigurationValid()) {
206+
throw new AuthInitializationException(
207+
"Only one of private key path or content should be configured for app authentication.");
208+
}
209+
return new ExtAppAuthenticatorRsaImpl(
210+
this.config.getRetry(),
211+
this.config.getApp().getAppId(),
212+
this.loadPrivateKeyFromAuthenticationConfig(this.config.getApp()),
213+
this.apiClientFactory.getLoginClient()
214+
);
215+
}
216+
throw new AuthInitializationException("Neither RSA private key nor certificate is configured.");
217+
}
218+
182219
private PrivateKey loadPrivateKeyFromAuthenticationConfig(BdkAuthenticationConfig config)
183220
throws AuthInitializationException {
184221
String privateKeyPath = "";

0 commit comments

Comments
 (0)