Skip to content

Commit

Permalink
AGNT-586 Add support for simplified key delivery (#811)
Browse files Browse the repository at this point in the history
* AGNT-585 Add support for simplified key delivery

* Remove useless constant package

* Update to use official symphony api

* Add javadoc
  • Loading branch information
FabienVSymphony authored Nov 14, 2024
1 parent 52e35fd commit ab6c666
Show file tree
Hide file tree
Showing 29 changed files with 423 additions and 110 deletions.
5 changes: 3 additions & 2 deletions symphony-bdk-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ dependencies {
}

// OpenAPI code generation
def apiBaseUrl = "https://raw.githubusercontent.com/finos/symphony-api-spec/30fcab0fe6eaa26dcc46e7dc5909467332ec8d0d"
def apiBaseUrl = "https://raw.githubusercontent.com/finos/symphony-api-spec/ee09734380226ac1109a1513156ceefac3bd5a1e"
def generatedFolder = "$buildDir/generated/openapi"
def apisToGenerate = [
Agent: 'agent/agent-api-public-deprecated.yaml',
Expand Down Expand Up @@ -116,7 +116,8 @@ apisToGenerate.each { api, path ->
supportingFiles: "false"
]
configOptions = [
dateLibrary: "java8"
dateLibrary: "java8",
sortParamsByRequiredFlag: "false"
]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.symphony.bdk.gen.api.AuditTrailApi;
import com.symphony.bdk.gen.api.ConnectionApi;
import com.symphony.bdk.gen.api.DatafeedApi;
import com.symphony.bdk.gen.api.DatahoseApi;
import com.symphony.bdk.gen.api.DefaultApi;
import com.symphony.bdk.gen.api.DisclaimerApi;
import com.symphony.bdk.gen.api.MessageApi;
Expand Down Expand Up @@ -145,7 +146,7 @@ public DatafeedLoop getDatafeedLoop(UserV2 botInfo) {
}

public DatahoseLoop getDatahoseLoop(UserV2 botInfo) {
return new DatahoseLoopImpl(new DatafeedApi(datahoseAgentClient), authSession, config, botInfo);
return new DatahoseLoopImpl(new DatafeedApi(datahoseAgentClient), authSession, config, botInfo, new DatahoseApi(datahoseAgentClient));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
import com.symphony.bdk.core.config.model.BdkAuthenticationConfig;
import com.symphony.bdk.core.config.model.BdkConfig;

import com.symphony.bdk.core.service.version.AgentVersionService;

import com.symphony.bdk.gen.api.SignalsApi;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apiguardian.api.API;
Expand Down Expand Up @@ -77,7 +81,8 @@ BotAuthenticator getBotAuthenticator() throws AuthInitializationException {
this.config.getCommonJwt(),
this.apiClientFactory.getLoginClient(),
this.apiClientFactory.getSessionAuthClient(),
this.apiClientFactory.getKeyAuthClient()
this.apiClientFactory.getKeyAuthClient(),
new AgentVersionService(new SignalsApi(this.apiClientFactory.getAgentClient()))
);
}
if (this.config.getBot().isRsaAuthenticationConfigured()) {
Expand All @@ -91,7 +96,8 @@ BotAuthenticator getBotAuthenticator() throws AuthInitializationException {
this.config.getCommonJwt(),
this.loadPrivateKeyFromAuthenticationConfig(this.config.getBot()),
this.apiClientFactory.getLoginClient(),
this.apiClientFactory.getRelayClient()
this.apiClientFactory.getRelayClient(),
new AgentVersionService(new SignalsApi(this.apiClientFactory.getAgentClient()))
);
}
throw new AuthInitializationException("Neither RSA private key nor certificate is configured.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;
import com.symphony.bdk.core.config.model.BdkCommonJwtConfig;
import com.symphony.bdk.core.config.model.BdkRetryConfig;
import com.symphony.bdk.core.service.version.AgentVersionService;
import com.symphony.bdk.gen.api.AuthenticationApi;
import com.symphony.bdk.gen.api.model.JwtToken;
import com.symphony.bdk.gen.api.model.Token;
Expand All @@ -29,14 +30,17 @@ public abstract class AbstractBotAuthenticator implements BotAuthenticator {
private final AuthenticationRetry<String> kmAuthenticationRetry;
private final AuthenticationRetry<Token> podAuthenticationRetry;
private final AuthenticationRetry<JwtToken> idmAuthenticationRetry;
private final AgentVersionService agentVersionService;

protected AbstractBotAuthenticator(BdkRetryConfig retryConfig,
@Nonnull BdkCommonJwtConfig commonJwtConfig, @Nonnull ApiClient loginApiClient) {
@Nonnull BdkCommonJwtConfig commonJwtConfig, @Nonnull ApiClient loginApiClient,
@Nonnull AgentVersionService agentVersionService) {
kmAuthenticationRetry = new AuthenticationRetry<>(retryConfig);
podAuthenticationRetry = new AuthenticationRetry<>(retryConfig);
idmAuthenticationRetry = new AuthenticationRetry<>(retryConfig);
this.commonJwtConfig = commonJwtConfig;
this.loginApiClient = loginApiClient;
this.agentVersionService = agentVersionService;
}

protected abstract String retrieveKeyManagerToken() throws AuthUnauthorizedException;
Expand Down Expand Up @@ -84,4 +88,8 @@ private JwtToken doRetrieveAuthorizationToken(ApiClient client, String sessionTo
public boolean isCommonJwtEnabled() {
return commonJwtConfig.getEnabled();
}

public AgentVersionService getAgentVersionService() {
return agentVersionService;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import com.symphony.bdk.core.auth.AuthSession;
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;
import com.symphony.bdk.core.auth.jwt.JwtHelper;
import com.symphony.bdk.core.service.version.model.AgentVersion;
import com.symphony.bdk.gen.api.model.Token;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.apiguardian.api.API;

import java.time.Duration;
import java.time.Instant;
import java.util.Optional;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -102,7 +104,9 @@ private void refreshAllTokens() throws AuthUnauthorizedException {
this.authorizationToken = authToken.getAuthorizationToken();
this.sessionToken = authToken.getToken();
refreshExpirationDate();
this.keyManagerToken = this.authenticator.retrieveKeyManagerToken();
if (!JwtHelper.isSkdEnabled(this.sessionToken) || !isSkdSupported()) {
this.keyManagerToken = this.authenticator.retrieveKeyManagerToken();
}
}

private void refreshExpirationDate() throws AuthUnauthorizedException {
Expand All @@ -121,4 +125,12 @@ private void refreshExpirationDate() throws AuthUnauthorizedException {
protected AbstractBotAuthenticator getAuthenticator() {
return this.authenticator;
}

protected boolean isSkdSupported() {
Optional<AgentVersion> currentVersion = authenticator.getAgentVersionService().retrieveAgentVersion();
if (currentVersion.isEmpty()) {
return false;
}
return currentVersion.get().isHigher(AgentVersion.AGENT_24_12);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException;
import com.symphony.bdk.core.config.model.BdkCommonJwtConfig;
import com.symphony.bdk.core.config.model.BdkRetryConfig;
import com.symphony.bdk.core.service.version.AgentVersionService;
import com.symphony.bdk.gen.api.CertificateAuthenticationApi;
import com.symphony.bdk.gen.api.model.Token;
import com.symphony.bdk.http.api.ApiClient;
Expand Down Expand Up @@ -33,8 +34,9 @@ public BotAuthenticatorCertImpl(
@Nonnull BdkCommonJwtConfig commonJwtConfig,
@Nonnull ApiClient loginClient,
@Nonnull ApiClient sessionAuthClient,
@Nonnull ApiClient keyAuthClient) {
super(retryConfig, commonJwtConfig, loginClient);
@Nonnull ApiClient keyAuthClient,
@Nonnull AgentVersionService agentVersionService) {
super(retryConfig, commonJwtConfig, loginClient, agentVersionService);
this.sessionAuthClient = sessionAuthClient;
this.keyAuthClient = keyAuthClient;
this.username = username;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
import com.symphony.bdk.core.auth.jwt.JwtHelper;
import com.symphony.bdk.core.config.model.BdkCommonJwtConfig;
import com.symphony.bdk.core.config.model.BdkRetryConfig;
import com.symphony.bdk.core.service.health.HealthService;
import com.symphony.bdk.core.service.version.AgentVersionService;
import com.symphony.bdk.gen.api.AuthenticationApi;
import com.symphony.bdk.gen.api.SystemApi;
import com.symphony.bdk.gen.api.model.AuthenticateRequest;
import com.symphony.bdk.gen.api.model.Token;
import com.symphony.bdk.gen.api.model.V3Health;
import com.symphony.bdk.http.api.ApiClient;
import com.symphony.bdk.http.api.ApiException;

Expand Down Expand Up @@ -38,9 +42,10 @@ public BotAuthenticatorRsaImpl(
@Nonnull BdkCommonJwtConfig commonJwtConfig,
@Nonnull PrivateKey privateKey,
@Nonnull ApiClient loginApiClient,
@Nonnull ApiClient relayApiClient
@Nonnull ApiClient relayApiClient,
@Nonnull AgentVersionService agentVersionService
) {
super(retryConfig, commonJwtConfig, loginApiClient);
super(retryConfig, commonJwtConfig, loginApiClient, agentVersionService);
this.username = username;
this.privateKey = privateKey;
this.relayApiClient = relayApiClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class JwtHelper {
// Expiration of the jwt, 5 minutes maximum, use a little less than that in case of different clock skews
public static final Long JWT_EXPIRATION_MILLIS = 240_000L;

private static final String SKD_CLAIM = "canUseSimplifiedKeyDelivery";

// PKCS#8 format
private static final String PEM_PRIVATE_START = "-----BEGIN PRIVATE KEY-----";
private static final String PEM_PRIVATE_END = "-----END PRIVATE KEY-----";
Expand Down Expand Up @@ -125,6 +127,19 @@ public static UserClaim validateJwt(String jwt, String certificate) throws AuthI
}
}

public static boolean isSkdEnabled(String jwt) {
try {
String claimsObj = extractDecodedClaims(dropBearer(jwt));
ObjectNode claims = mapper.readValue(claimsObj, ObjectNode.class);
if (claims.has(SKD_CLAIM) && claims.get(SKD_CLAIM).isBoolean()) {
return claims.get(SKD_CLAIM).asBoolean();
}
} catch (Exception e) {
return false;
}
return false;
}

/**
* Extract the expiration date (in seconds) from the input jwt. If the jwt uses the Beare prefix, it
* will be removed before parsing. This function is not validating the jwt signature.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ protected void runLoop() throws Throwable {
private Void readAndHandleEvents() throws ApiException {
List<V4Event> events = this.datafeedApi.v4DatafeedIdReadGet(
datafeedId,
null,
authSession.getSessionToken(),
authSession.getKeyManagerToken(),
null
authSession.getKeyManagerToken()
);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder;
import com.symphony.bdk.core.service.datafeed.DatahoseLoop;
import com.symphony.bdk.gen.api.DatafeedApi;
import com.symphony.bdk.gen.api.DatahoseApi;
import com.symphony.bdk.gen.api.model.UserV2;
import com.symphony.bdk.gen.api.model.V5EventList;
import com.symphony.bdk.gen.api.model.V5EventsReadBody;
Expand All @@ -29,9 +30,12 @@ public class DatahoseLoopImpl extends AbstractAckIdEventLoop implements Datahose
private final String tag;
private final List<String> filters;
private final RetryWithRecovery<Object> readEvents;
private final DatahoseApi datahoseApi;

public DatahoseLoopImpl(DatafeedApi datafeedApi, AuthSession authSession, BdkConfig config, UserV2 botInfo) {
public DatahoseLoopImpl(DatafeedApi datafeedApi, AuthSession authSession, BdkConfig config, UserV2 botInfo,
DatahoseApi datahoseApi) {
super(datafeedApi, authSession, config, botInfo);
this.datahoseApi = datahoseApi;

String untruncatedTag = config.getDatahose().getTag();
if (StringUtils.isEmpty(untruncatedTag)) {
Expand Down Expand Up @@ -65,7 +69,7 @@ protected void runLoop() throws Throwable {

@Override
protected V5EventList readEvents() throws ApiException {
return this.datafeedApi.readEvents(this.authSession.getSessionToken(), this.authSession.getKeyManagerToken(),
return this.datahoseApi.readEvents(this.authSession.getSessionToken(), this.authSession.getKeyManagerToken(),
new V5EventsReadBody().ackId(this.ackId).eventTypes(this.filters).tag(this.tag).type(DATAHOSE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,10 @@ public List<V4Message> listMessages(@Nonnull String streamId, @Nonnull Instant s
@Nonnull PaginationAttribute pagination) {
return executeAndRetry("getMessages", messageApi.getApiClient().getBasePath(),
() -> messagesApi.v4StreamSidMessageGet(toUrlSafeIdIfNeeded(streamId), getEpochMillis(since),
authSession.getSessionToken(), authSession.getKeyManagerToken(), pagination.getSkip(),
pagination.getLimit()));
pagination.getSkip(),
pagination.getLimit(),
authSession.getSessionToken(),
authSession.getKeyManagerToken()));
}

/**
Expand All @@ -189,8 +191,8 @@ public List<V4Message> listMessages(@Nonnull String streamId, @Nonnull Instant s
*/
public List<V4Message> listMessages(@Nonnull String streamId, @Nonnull Instant since) {
return executeAndRetry("getMessages", messageApi.getApiClient().getBasePath(),
() -> messagesApi.v4StreamSidMessageGet(toUrlSafeIdIfNeeded(streamId), getEpochMillis(since),
authSession.getSessionToken(), authSession.getKeyManagerToken(), null, null));
() -> messagesApi.v4StreamSidMessageGet(toUrlSafeIdIfNeeded(streamId), getEpochMillis(since), null, null,
authSession.getSessionToken(), authSession.getKeyManagerToken()));
}

/**
Expand Down Expand Up @@ -229,14 +231,17 @@ public List<V4Message> searchMessages(@Nonnull MessageSearchQuery query, @Nullab
@Nullable SortDir sortDir) {
validateMessageSearchQuery(query);
return executeAndRetry("searchMessages", messageApi.getApiClient().getBasePath(),
() -> messagesApi.v1MessageSearchPost(authSession.getSessionToken(), authSession.getKeyManagerToken(), query,
() -> messagesApi.v1MessageSearchPost(
pagination != null ? pagination.getSkip() : null,
pagination != null ? pagination.getLimit() : null,
// 'scope' argument is not documented for POST search, but is for GET:
// "Specifies against which connector to perform the search, either Symphony content or one connector.".
// We will assume (for now) that it as no real usage.
null,
sortDir != null ? sortDir.name().toLowerCase() : null)
sortDir != null ? sortDir.name().toLowerCase() : null,
null, // tier is not supported for now
authSession.getSessionToken(), authSession.getKeyManagerToken(), query
)
);
}

Expand Down Expand Up @@ -489,8 +494,8 @@ public List<StreamAttachmentItem> listAttachments(@Nonnull String streamId, @Nul
final String sortDir = sort == null ? AttachmentSort.ASC.name() : sort.name();

return executeAndRetry("listAttachments", streamsApi.getApiClient().getBasePath(),
() -> streamsApi.v1StreamsSidAttachmentsGet(toUrlSafeIdIfNeeded(streamId), authSession.getSessionToken(),
getEpochMillis(since), getEpochMillis(to), limit, sortDir));
() -> streamsApi.v1StreamsSidAttachmentsGet(toUrlSafeIdIfNeeded(streamId),
getEpochMillis(since), getEpochMillis(to), limit, sortDir, authSession.getSessionToken()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public List<V2Presence> listPresences(@Nullable Long lastUserId, @Nullable Integ
@Override
public V2Presence getUserPresence(@Nonnull Long userId, @Nullable Boolean local) {
return executeAndRetry("getUserPresence",
() -> presenceApi.v3UserUidPresenceGet(userId, authSession.getSessionToken(), local));
() -> presenceApi.v3UserUidPresenceGet(userId, local, authSession.getSessionToken()));
}

/**
Expand All @@ -104,7 +104,7 @@ public void externalPresenceInterest(@Nonnull List<Long> userIds) {
public V2Presence setPresence(@Nonnull PresenceStatus status, @Nullable Boolean soft) {
V2PresenceStatus presenceStatus = new V2PresenceStatus().category(status.name());
return executeAndRetry("setPresence",
() -> presenceApi.v2UserPresencePost(authSession.getSessionToken(), presenceStatus, soft));
() -> presenceApi.v2UserPresencePost(authSession.getSessionToken(), soft, presenceStatus));
}

/**
Expand Down Expand Up @@ -141,7 +141,7 @@ public String deletePresenceFeed(@Nonnull String feedId) {
public V2Presence setUserPresence(@Nonnull Long userId, @Nonnull PresenceStatus status, @Nullable Boolean soft) {
V2UserPresence userPresence = new V2UserPresence().userId(userId).category(status.name());
return executeAndRetry("setUserPresence",
() -> presenceApi.v3UserPresencePost(authSession.getSessionToken(), userPresence, soft));
() -> presenceApi.v3UserPresencePost(authSession.getSessionToken(), soft, userPresence));
}

private <T> T executeAndRetry(String name, SupplierWithApiException<T> supplier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public Signal getSignal(@Nonnull String id) {
@Override
public Signal createSignal(@Nonnull BaseSignal signal) {
return executeAndRetry("createSignal",
() -> signalsApi.v1SignalsCreatePost(authSession.getSessionToken(), signal, authSession.getKeyManagerToken()));
() -> signalsApi.v1SignalsCreatePost(authSession.getSessionToken(), authSession.getKeyManagerToken(), signal));
}

/**
Expand All @@ -129,8 +129,7 @@ public Signal createSignal(@Nonnull BaseSignal signal) {
@Override
public Signal updateSignal(@Nonnull String id, @Nonnull BaseSignal signal) {
return executeAndRetry("updateSignal",
() -> signalsApi.v1SignalsIdUpdatePost(authSession.getSessionToken(), id, signal,
authSession.getKeyManagerToken()));
() -> signalsApi.v1SignalsIdUpdatePost(authSession.getSessionToken(), authSession.getKeyManagerToken(), id, signal));
}

/**
Expand All @@ -139,7 +138,7 @@ public Signal updateSignal(@Nonnull String id, @Nonnull BaseSignal signal) {
@Override
public void deleteSignal(@Nonnull String id) {
executeAndRetry("deleteSignal",
() -> signalsApi.v1SignalsIdDeletePost(authSession.getSessionToken(), id, authSession.getKeyManagerToken()));
() -> signalsApi.v1SignalsIdDeletePost(authSession.getSessionToken(), authSession.getKeyManagerToken(), id));
}

/**
Expand All @@ -149,7 +148,7 @@ public void deleteSignal(@Nonnull String id) {
public ChannelSubscriptionResponse subscribeUsersToSignal(@Nonnull String id, @Nullable Boolean pushed,
@Nullable List<Long> userIds) {
return executeAndRetry("subscribeUsersToSignal",
() -> signalsApi.v1SignalsIdSubscribePost(authSession.getSessionToken(), id, authSession.getKeyManagerToken(),
() -> signalsApi.v1SignalsIdSubscribePost(authSession.getSessionToken(), authSession.getKeyManagerToken(), id,
pushed, userIds));
}

Expand Down
Loading

0 comments on commit ab6c666

Please sign in to comment.