Skip to content

Commit

Permalink
Merge pull request #146 from jembi/JMPI-761-update-browse-component
Browse files Browse the repository at this point in the history
Jmpi 761 update browse component
  • Loading branch information
issambaccouch authored Dec 4, 2023
2 parents 4331032 + 2229610 commit 0c94400
Show file tree
Hide file tree
Showing 37 changed files with 539 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import org.jembi.jempi.AppConfig;
import org.jembi.jempi.api.user.UserSession;

import static com.softwaremill.session.javadsl.SessionTransports.CookieST;
import static com.softwaremill.session.javadsl.SessionTransports.HeaderST;

public final class HttpServerSessionManager extends SessionManager<UserSession> {
private static final Logger LOGGER = LogManager.getLogger(HttpServerSessionManager.class);
Expand All @@ -30,7 +30,7 @@ public HttpServerSessionManager(final MessageDispatcher dispatcher) {
refreshable = new Refreshable<>(this, REFRESH_TOKEN_STORAGE, dispatcher);

// set the session transport - based on Cookies (or Headers)
sessionTransport = CookieST;
sessionTransport = HeaderST;
}

public Refreshable<UserSession> getRefreshable() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ private Route routeLoginWithKeycloakRequest(final CheckHeader<UserSession> check
}

private Route routeCurrentUser() {
return this.httpServer.requiredSession(sessionManager.getRefreshable(), sessionManager.getSessionTransport(), session -> {
if (session != null) {
LOGGER.info("Current session: {}", session.getEmail());
return this.httpServer.optionalSession(sessionManager.getRefreshable(), sessionManager.getSessionTransport(), session -> {
if (session.isPresent()) {
LOGGER.info("Current session: {}", session.get().getUsername());
return complete(StatusCodes.OK, session, Jackson.marshaller());
}
LOGGER.info("No active session");
return complete(StatusCodes.FORBIDDEN);
return complete(StatusCodes.OK, "");
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
"turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests",
"public-key-cache-ttl", "policy-enforcer", "ignore-oauth-query-parameter", "verify-token-audience"})
public final class AkkaAdapterConfig extends AdapterConfig {
@JsonProperty("frontend-kc-url")
private String frontendKcUri;

@JsonProperty("redirect-uri")
private String redirectUri;

Expand Down Expand Up @@ -50,6 +53,10 @@ public Map<String, Object> getCredentials() {
return credentials;
}

String getFrontendKcUri() {
return EnvUtil.replace(this.frontendKcUri);
}

String getRedirectUri() {
return EnvUtil.replace(this.redirectUri);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.jembi.jempi.api.keyCloak;

import java.security.PublicKey;

import org.keycloak.TokenVerifier;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
import org.keycloak.adapters.rotation.AdapterTokenVerifier.VerifiedTokens;
import org.keycloak.adapters.rotation.PublicKeyLocator;
import org.keycloak.common.VerificationException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.JsonWebToken;


// Code taken from the super class org.keycloak.adapters.rotation.AdapterTokenVerifier
// Since they are static methods we need to redeclare them here
// The reason for override is that within JeMPI keycloak has 2 adddress which it is accessed from
// 1) The frontend url (KC_FRONTEND_URL) which the ui uses, and 2) the backend url (KC_API_URL), which the api uses
// of which the default verification assumes the address are the same.
// This change also us to use the appropiate url when verifying the tokenss

public final class KeyCloakAdapterTokenVerifier extends AdapterTokenVerifier {

public static VerifiedTokens verifyTokens(final String accessTokenString, final String idTokenString, final KeycloakDeployment deployment, final AkkaAdapterConfig keycloakConfig) throws VerificationException {
TokenVerifier<AccessToken> tokenVerifier = createVerifier(accessTokenString, deployment, true, AccessToken.class, keycloakConfig);
AccessToken accessToken = tokenVerifier.verify().getToken();

if (idTokenString != null) {
IDToken idToken = TokenVerifier.create(idTokenString, IDToken.class).getToken();
TokenVerifier<IDToken> idTokenVerifier = TokenVerifier.createWithoutSignature(idToken);

idTokenVerifier.audience(deployment.getResourceName());
idTokenVerifier.issuedFor(deployment.getResourceName());

idTokenVerifier.verify();
return new VerifiedTokens(accessToken, idToken);
} else {
return new VerifiedTokens(accessToken, null);
}
}

private static PublicKey getPublicKey(final String kid, final KeycloakDeployment deployment) throws VerificationException {
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();

PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
if (publicKey == null) {
throw new VerificationException("Didn't find publicKey for specified kid");
}

return publicKey;
}

public static <T extends JsonWebToken> TokenVerifier<T> createVerifier(final String tokenString, final KeycloakDeployment deployment, final boolean withDefaultChecks, final Class<T> tokenClass, final AkkaAdapterConfig keycloakConfig) throws VerificationException {
TokenVerifier<T> tokenVerifier = TokenVerifier.create(tokenString, tokenClass);

tokenVerifier.withDefaultChecks().realmUrl(String.format("%s/realms/%s", keycloakConfig.getFrontendKcUri(), deployment.getRealm()));

String kid = tokenVerifier.getHeader().getKeyId();
PublicKey publicKey = getPublicKey(kid, deployment);
tokenVerifier.publicKey(publicKey);

return tokenVerifier;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.jembi.jempi.api.persistance.postgres.queries.UserQueries;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
Expand Down Expand Up @@ -44,8 +43,8 @@ private User loginWithKeycloakHandler(final OAuthCodeRequestPayload payload) {
String tokenString = tokenResponse.getToken();
String idTokenString = tokenResponse.getIdToken();

AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenString, idTokenString,
keycloak);
KeyCloakAdapterTokenVerifier.VerifiedTokens tokens = KeyCloakAdapterTokenVerifier.verifyTokens(tokenString, idTokenString,
keycloak, keycloakConfig);
LOGGER.debug("Token Verification succeeded!");
AccessToken token = tokens.getAccessToken();
LOGGER.debug("Is user already registered?");
Expand Down
11 changes: 10 additions & 1 deletion JeMPI_Apps/JeMPI_API_KC/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,30 @@ akka-http-cors {
# List of headers (other than simple response headers) that browsers are allowed to access.
# If not empty, this list is returned as part of the `Access-Control-Expose-Headers`
# header in the actual response.
exposed-headers = []
exposed-headers = ["Set-Authorization"]

# When set, the amount of seconds the browser is allowed to cache the results of a preflight request.
# This value is returned as part of the `Access-Control-Max-Age` preflight response header.
# If `null`, the header is not added to the preflight response.
max-age = 1800 seconds
}

# Note: For authentication with use the header session transport (com.softwaremill.session.javadsl.SessionTransports.HeaderST)
# The cookie setting here are mainly for csrf, which we are not yet full utilizing because of limitations of cross-domain cookie setting (which requires https)
# Might need to consider an alternative approach (outside akka.http.session) if we still want this feature, or consider removing it all together
# Keeping it here for posterity for now.
akka.http.session {
server-secret = ${JEMPI_SESSION_SECRET}
encrypt-data = true
cookie {
secure = true
# This has to be None, as we access the api from another domain
same-site = "None"
}
header {
send-to-client-name = "Set-Authorization"
get-from-client-name = "Authorization"
}
csrf {
cookie {
name = "XSRF-TOKEN"
Expand Down
1 change: 1 addition & 0 deletions JeMPI_Apps/JeMPI_API_KC/src/main/resources/keycloak.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"realm" : "${env.KC_REALM_NAME}",
"resource" : "${env.KC_JEMPI_CLIENT_ID}",
"auth-server-url" : "${env.KC_API_URL}",
"frontend-kc-url": "${env.KC_FRONTEND_URL}",
"redirect-uri": "${env.KC_JEMPI_ROOT_URL}/login",
"ssl-required" : "none",
"use-resource-role-mappings" : false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,20 @@ private static String getSimpleSearchQueryFilters(
Integer distance = param.distance();
String value = param.value();
if (distance == -1) {
gqlFilters.add("le(" + recordType + "." + fieldName + ", \"" + value + "\")");
if (value.contains("_")) {
gqlFilters.add("ge(" + recordType + "." + fieldName + ", \"" + value.substring(0, value.indexOf("_"))
+ "\") AND le("
+ recordType + "." + fieldName + ", \"" + value.substring(value.indexOf("_") + 1) + "\")");
} else {
gqlFilters.add("le(" + recordType + "." + fieldName + ", \"" + value + "\")");
}
} else if (distance == 0) {
gqlFilters.add("eq(" + recordType + "." + fieldName + ", \"" + value + "\")");
if (value.contains("_")) {
gqlFilters.add(
"eq(" + recordType + "." + fieldName + ", \"" + value.substring(0, value.indexOf("_")) + "\")");
} else {
gqlFilters.add("eq(" + recordType + "." + fieldName + ", \"" + value + "\")");
}
} else {
gqlFilters.add("match(" + recordType + "." + fieldName + ", $" + fieldName + ", " + distance + ")");
}
Expand Down Expand Up @@ -452,17 +463,13 @@ private static DgraphExpandedGoldenRecords searchGoldenRecords(
final Boolean sortAsc) {
String gqlFunc = getSearchQueryFunc(RecordType.GoldenRecord, offset, limit, sortBy, sortAsc);
String gqlPagination = getSearchQueryPagination(RecordType.GoldenRecord, gqlFilters);

String gql = "query search(" + String.join(", ", gqlArgs) + ") {\n";
gql += String.format(Locale.ROOT, "all(%s) @filter(%s)", gqlFunc, gqlFilters);
gql += "{\n";
gql += CustomDgraphConstants.EXPANDED_GOLDEN_RECORD_FIELD_NAMES;
gql += "}\n";
gql += gqlPagination;
gql += "}";

LOGGER.debug("Search Golden Records Query {}", gql);
LOGGER.debug("Search Golden Records Variables {}", gqlVars);
return runExpandedGoldenRecordsQuery(gql, gqlVars);
}

Expand All @@ -472,7 +479,6 @@ static DgraphExpandedGoldenRecords simpleSearchGoldenRecords(
final Integer limit,
final String sortBy,
final Boolean sortAsc) {
LOGGER.debug("Simple Search Golden Records Params {}", params);
String gqlFilters = getSimpleSearchQueryFilters(RecordType.GoldenRecord, params);
List<String> gqlArgs = getSimpleSearchQueryArguments(params);
HashMap<String, String> gqlVars = getSimpleSearchQueryVariables(params);
Expand All @@ -486,7 +492,6 @@ static DgraphExpandedGoldenRecords customSearchGoldenRecords(
final Integer limit,
final String sortBy,
final Boolean sortAsc) {
LOGGER.debug("Custom Search Golden Records Params {}", payloads);
String gqlFilters = getCustomSearchQueryFilters(RecordType.GoldenRecord, payloads);
List<String> gqlArgs = getCustomSearchQueryArguments(payloads);
HashMap<String, String> gqlVars = getCustomSearchQueryVariables(payloads);
Expand All @@ -511,9 +516,6 @@ private static DgraphInteractions searchInteractions(
gql += "}\n";
gql += gqlPagination;
gql += "}";

LOGGER.debug("Simple Search Interactions Query {}", gql);
LOGGER.debug("Simple Search Interactions Variables {}", gqlVars);
return runInteractionsQuery(gql, gqlVars);
}

Expand Down Expand Up @@ -548,8 +550,6 @@ a as count(GoldenRecord.interactions)}
gql += gqlPagination;
gql += "}";

LOGGER.debug("Filter Gids Query {}", gql);
LOGGER.debug("Filter Gids Variables {}", gqlVars);
return Boolean.TRUE.equals(getInteractionCount)
? Either.right(runFilterGidsWithInteractionCountQuery(gql, gqlVars))
: Either.left(runfilterGidsQuery(gql, gqlVars));
Expand All @@ -560,7 +560,6 @@ static Either<DgraphPaginatedUidList, DgraphPaginationUidListWithInteractionCoun
final LocalDateTime createdAt,
final PaginationOptions paginationOptions,
final Boolean getInteractionCount) {
LOGGER.debug("Filter Gids Params {}", params);
String dateFilter = String.format(Locale.ROOT, "le(GoldenRecord.aux_date_created,\"%s\")", createdAt);
String filter = getSimpleSearchQueryFilters(RecordType.GoldenRecord, params);
String gqlFilters = !filter.isEmpty()
Expand All @@ -577,7 +576,6 @@ static DgraphInteractions simpleSearchInteractions(
final Integer limit,
final String sortBy,
final Boolean sortAsc) {
LOGGER.debug("Simple Search Interactions Params {}", params);
String gqlFilters = getSimpleSearchQueryFilters(RecordType.Interaction, params);
List<String> gqlArgs = getSimpleSearchQueryArguments(params);
HashMap<String, String> gqlVars = getSimpleSearchQueryVariables(params);
Expand All @@ -591,7 +589,6 @@ static DgraphInteractions customSearchInteractions(
final Integer limit,
final String sortBy,
final Boolean sortAsc) {
LOGGER.debug("Simple Search Interactions Params {}", payloads);
String gqlFilters = getCustomSearchQueryFilters(RecordType.Interaction, payloads);
List<String> gqlArgs = getCustomSearchQueryArguments(payloads);
HashMap<String, String> gqlVars = getCustomSearchQueryVariables(payloads);
Expand Down Expand Up @@ -656,10 +653,7 @@ static DgraphGoldenRecords findGoldenRecords(final ApiModels.ApiCrFindRequest re
for (var o : req.operands()) {
map.put("$" + camelToSnake(o.operand().name()), o.operand().value());
}
LOGGER.debug("{}", "\n" + query);
LOGGER.debug("{}", map);
final var dgraphGoldenRecords = runGoldenRecordsQuery(query, map);
LOGGER.debug("{}", dgraphGoldenRecords);
return dgraphGoldenRecords;
}

Expand Down
9 changes: 5 additions & 4 deletions JeMPI_Apps/JeMPI_UI/.env.local
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ REACT_APP_JEMPI_BASE_API_PORT=50000
REACT_APP_MAX_UPLOAD_CSV_SIZE_IN_MEGABYTES="128"
REACT_APP_SHOW_BRAND_LOGO=false
REACT_APP_MOCK_BACKEND=false
REACT_APP_ENABLE_SSO=false
REACT_APP_ENABLE_SSO=true

KC_FRONTEND_URL=http://localhost:9088
KC_REALM_NAME=platform-realm
KC_JEMPI_CLIENT_ID=jempi-oauth

KC_FRONTEND_URL=http://localhost:8080
KC_REALM_NAME=jempi-dev
KC_JEMPI_CLIENT_ID=jempi-oauth
2 changes: 1 addition & 1 deletion JeMPI_Apps/JeMPI_UI/build-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ envsubst < $PROJECT_DEVOPS_DIR/conf/ui/.env > ./.env

[ -z $(docker images -q ${UI_IMAGE}) ] || docker rmi ${UI_IMAGE}
docker system prune --volumes -f
docker build --tag $UI_IMAGE --target production-stage .
docker build --tag $UI_IMAGE --target $NODE_ENV-stage .
Loading

0 comments on commit 0c94400

Please sign in to comment.