Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AGNT-586 Add support for simplified key delivery #811

Merged
merged 4 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
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/FabienVSymphony/symphony-api-spec/refs/heads/AGNT-585/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be finos link

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
Loading