feat(frontend): align frontend sso code with refactors (#9506)

This commit is contained in:
RyanHolstien 2023-12-26 14:34:10 -06:00 committed by GitHub
parent 1e64a75339
commit 651998de44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 768 additions and 144 deletions

View File

@ -1,13 +1,9 @@
package auth;
import static auth.AuthUtils.*;
import static auth.sso.oidc.OidcConfigs.*;
import static utils.ConfigUtil.*;
import auth.sso.SsoConfigs;
import auth.sso.SsoManager;
import auth.sso.oidc.OidcConfigs;
import auth.sso.oidc.OidcProvider;
import client.AuthServiceClient;
import com.datahub.authentication.Actor;
import com.datahub.authentication.ActorType;
@ -23,14 +19,11 @@ import com.linkedin.util.Configuration;
import config.ConfigurationProvider;
import controllers.SsoCallbackController;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.play.LogoutController;
@ -45,6 +38,7 @@ import play.cache.SyncCacheApi;
import utils.ConfigUtil;
/** Responsible for configuring, validating, and providing authentication related components. */
@Slf4j
public class AuthModule extends AbstractModule {
/**
@ -58,6 +52,7 @@ public class AuthModule extends AbstractModule {
private static final String PAC4J_SESSIONSTORE_PROVIDER_CONF = "pac4j.sessionStore.provider";
private static final String ENTITY_CLIENT_RETRY_INTERVAL = "entityClient.retryInterval";
private static final String ENTITY_CLIENT_NUM_RETRIES = "entityClient.numRetries";
private static final String GET_SSO_SETTINGS_ENDPOINT = "auth/getSsoSettings";
private final com.typesafe.config.Config _configs;
@ -111,6 +106,7 @@ public class AuthModule extends AbstractModule {
Authentication.class,
SystemEntityClient.class,
AuthServiceClient.class,
org.pac4j.core.config.Config.class,
com.typesafe.config.Config.class));
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(
@ -124,34 +120,20 @@ public class AuthModule extends AbstractModule {
@Provides
@Singleton
protected Config provideConfig(SsoManager ssoManager) {
if (ssoManager.isSsoEnabled()) {
final Clients clients = new Clients();
final List<Client> clientList = new ArrayList<>();
clientList.add(ssoManager.getSsoProvider().client());
clients.setClients(clientList);
final Config config = new Config(clients);
config.setHttpActionAdapter(new PlayHttpActionAdapter());
return config;
}
return new Config();
protected Config provideConfig() {
Config config = new Config();
config.setHttpActionAdapter(new PlayHttpActionAdapter());
return config;
}
@Provides
@Singleton
protected SsoManager provideSsoManager() {
SsoManager manager = new SsoManager();
// Seed the SSO manager with a default SSO provider.
if (isSsoEnabled(_configs)) {
SsoConfigs ssoConfigs = new SsoConfigs(_configs);
if (ssoConfigs.isOidcEnabled()) {
// Register OIDC Provider, add to list of managers.
OidcConfigs oidcConfigs = new OidcConfigs(_configs);
OidcProvider oidcProvider = new OidcProvider(oidcConfigs);
// Set the default SSO provider to this OIDC client.
manager.setSsoProvider(oidcProvider);
}
}
protected SsoManager provideSsoManager(
Authentication systemAuthentication, CloseableHttpClient httpClient) {
SsoManager manager =
new SsoManager(
_configs, systemAuthentication, getSsoSettingsRequestUrl(_configs), httpClient);
manager.initializeSsoProvider();
return manager;
}
@ -191,33 +173,16 @@ public class AuthModule extends AbstractModule {
configurationProvider.getCache().getClient().getEntityClient());
}
@Provides
@Singleton
protected CloseableHttpClient provideHttpClient() {
return HttpClients.createDefault();
}
@Provides
@Singleton
protected AuthServiceClient provideAuthClient(
Authentication systemAuthentication, CloseableHttpClient httpClient) {
// Init a GMS auth client
final String metadataServiceHost =
_configs.hasPath(METADATA_SERVICE_HOST_CONFIG_PATH)
? _configs.getString(METADATA_SERVICE_HOST_CONFIG_PATH)
: Configuration.getEnvironmentVariable(GMS_HOST_ENV_VAR, DEFAULT_GMS_HOST);
final String metadataServiceHost = getMetadataServiceHost(_configs);
final int metadataServicePort =
_configs.hasPath(METADATA_SERVICE_PORT_CONFIG_PATH)
? _configs.getInt(METADATA_SERVICE_PORT_CONFIG_PATH)
: Integer.parseInt(
Configuration.getEnvironmentVariable(GMS_PORT_ENV_VAR, DEFAULT_GMS_PORT));
final int metadataServicePort = getMetadataServicePort(_configs);
final Boolean metadataServiceUseSsl =
_configs.hasPath(METADATA_SERVICE_USE_SSL_CONFIG_PATH)
? _configs.getBoolean(METADATA_SERVICE_USE_SSL_CONFIG_PATH)
: Boolean.parseBoolean(
Configuration.getEnvironmentVariable(GMS_USE_SSL_ENV_VAR, DEFAULT_GMS_USE_SSL));
final boolean metadataServiceUseSsl = doesMetadataServiceUseSsl(_configs);
return new AuthServiceClient(
metadataServiceHost,
@ -227,6 +192,12 @@ public class AuthModule extends AbstractModule {
httpClient);
}
@Provides
@Singleton
protected CloseableHttpClient provideHttpClient() {
return HttpClients.createDefault();
}
private com.linkedin.restli.client.Client buildRestliClient() {
final String metadataServiceHost =
utils.ConfigUtil.getString(
@ -255,16 +226,33 @@ public class AuthModule extends AbstractModule {
metadataServiceSslProtocol);
}
protected boolean isSsoEnabled(com.typesafe.config.Config configs) {
// If OIDC is enabled, we infer SSO to be enabled.
return configs.hasPath(OIDC_ENABLED_CONFIG_PATH)
&& Boolean.TRUE.equals(Boolean.parseBoolean(configs.getString(OIDC_ENABLED_CONFIG_PATH)));
protected boolean doesMetadataServiceUseSsl(com.typesafe.config.Config configs) {
return configs.hasPath(METADATA_SERVICE_USE_SSL_CONFIG_PATH)
? configs.getBoolean(METADATA_SERVICE_USE_SSL_CONFIG_PATH)
: Boolean.parseBoolean(
Configuration.getEnvironmentVariable(GMS_USE_SSL_ENV_VAR, DEFAULT_GMS_USE_SSL));
}
protected boolean isMetadataServiceAuthEnabled(com.typesafe.config.Config configs) {
// If OIDC is enabled, we infer SSO to be enabled.
return configs.hasPath(METADATA_SERVICE_AUTH_ENABLED_CONFIG_PATH)
&& Boolean.TRUE.equals(
Boolean.parseBoolean(configs.getString(METADATA_SERVICE_AUTH_ENABLED_CONFIG_PATH)));
protected String getMetadataServiceHost(com.typesafe.config.Config configs) {
return configs.hasPath(METADATA_SERVICE_HOST_CONFIG_PATH)
? configs.getString(METADATA_SERVICE_HOST_CONFIG_PATH)
: Configuration.getEnvironmentVariable(GMS_HOST_ENV_VAR, DEFAULT_GMS_HOST);
}
protected Integer getMetadataServicePort(com.typesafe.config.Config configs) {
return configs.hasPath(METADATA_SERVICE_PORT_CONFIG_PATH)
? configs.getInt(METADATA_SERVICE_PORT_CONFIG_PATH)
: Integer.parseInt(
Configuration.getEnvironmentVariable(GMS_PORT_ENV_VAR, DEFAULT_GMS_PORT));
}
protected String getSsoSettingsRequestUrl(com.typesafe.config.Config configs) {
final String protocol = doesMetadataServiceUseSsl(configs) ? "https" : "http";
final String metadataServiceHost = getMetadataServiceHost(configs);
final Integer metadataServicePort = getMetadataServicePort(configs);
return String.format(
"%s://%s:%s/%s",
protocol, metadataServiceHost, metadataServicePort, GET_SSO_SETTINGS_ENDPOINT);
}
}

View File

@ -56,6 +56,26 @@ public class AuthUtils {
public static final String TITLE = "title";
public static final String INVITE_TOKEN = "inviteToken";
public static final String RESET_TOKEN = "resetToken";
public static final String BASE_URL = "baseUrl";
public static final String OIDC_ENABLED = "oidcEnabled";
public static final String CLIENT_ID = "clientId";
public static final String CLIENT_SECRET = "clientSecret";
public static final String DISCOVERY_URI = "discoveryUri";
public static final String USER_NAME_CLAIM = "userNameClaim";
public static final String USER_NAME_CLAIM_REGEX = "userNameClaimRegex";
public static final String SCOPE = "scope";
public static final String CLIENT_NAME = "clientName";
public static final String CLIENT_AUTHENTICATION_METHOD = "clientAuthenticationMethod";
public static final String JIT_PROVISIONING_ENABLED = "jitProvisioningEnabled";
public static final String PRE_PROVISIONING_REQUIRED = "preProvisioningRequired";
public static final String EXTRACT_GROUPS_ENABLED = "extractGroupsEnabled";
public static final String GROUPS_CLAIM = "groupsClaim";
public static final String RESPONSE_TYPE = "responseType";
public static final String RESPONSE_MODE = "responseMode";
public static final String USE_NONCE = "useNonce";
public static final String READ_TIMEOUT = "readTimeout";
public static final String EXTRACT_JWT_ACCESS_TOKEN_CLAIMS = "extractJwtAccessTokenClaims";
/**
* Determines whether the inbound request should be forward to downstream Metadata Service. Today,

View File

@ -1,8 +1,16 @@
package auth.sso;
import static auth.ConfigUtil.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/** Class responsible for extracting and validating top-level SSO related configurations. */
import static auth.AuthUtils.*;
/**
* Class responsible for extracting and validating top-level SSO related configurations. TODO:
* Refactor SsoConfigs to have OidcConfigs and other identity provider specific configs as instance
* variables. SSoManager should ideally not know about identity provider specific configs.
*/
public class SsoConfigs {
/** Required configs */
@ -22,16 +30,11 @@ public class SsoConfigs {
private final String _authSuccessRedirectPath;
private final Boolean _oidcEnabled;
public SsoConfigs(final com.typesafe.config.Config configs) {
_authBaseUrl = getRequired(configs, AUTH_BASE_URL_CONFIG_PATH);
_authBaseCallbackPath =
getOptional(configs, AUTH_BASE_CALLBACK_PATH_CONFIG_PATH, DEFAULT_BASE_CALLBACK_PATH);
_authSuccessRedirectPath =
getOptional(configs, AUTH_SUCCESS_REDIRECT_PATH_CONFIG_PATH, DEFAULT_SUCCESS_REDIRECT_PATH);
_oidcEnabled =
configs.hasPath(OIDC_ENABLED_CONFIG_PATH)
&& Boolean.TRUE.equals(
Boolean.parseBoolean(configs.getString(OIDC_ENABLED_CONFIG_PATH)));
public SsoConfigs(Builder<?> builder) {
_authBaseUrl = builder._authBaseUrl;
_authBaseCallbackPath = builder._authBaseCallbackPath;
_authSuccessRedirectPath = builder._authSuccessRedirectPath;
_oidcEnabled = builder._oidcEnabled;
}
public String getAuthBaseUrl() {
@ -49,4 +52,52 @@ public class SsoConfigs {
public Boolean isOidcEnabled() {
return _oidcEnabled;
}
public static class Builder<T extends Builder<T>> {
protected String _authBaseUrl = null;
private String _authBaseCallbackPath = DEFAULT_BASE_CALLBACK_PATH;
private String _authSuccessRedirectPath = DEFAULT_SUCCESS_REDIRECT_PATH;
protected Boolean _oidcEnabled = false;
private final ObjectMapper _objectMapper = new ObjectMapper();
protected JsonNode jsonNode = null;
// No need to check if changes are made since this method is only called at start-up.
public Builder from(final com.typesafe.config.Config configs) {
if (configs.hasPath(AUTH_BASE_URL_CONFIG_PATH)) {
_authBaseUrl = configs.getString(AUTH_BASE_URL_CONFIG_PATH);
}
if (configs.hasPath(AUTH_BASE_CALLBACK_PATH_CONFIG_PATH)) {
_authBaseCallbackPath = configs.getString(AUTH_BASE_CALLBACK_PATH_CONFIG_PATH);
}
if (configs.hasPath(OIDC_ENABLED_CONFIG_PATH)) {
_oidcEnabled =
Boolean.TRUE.equals(Boolean.parseBoolean(configs.getString(OIDC_ENABLED_CONFIG_PATH)));
}
if (configs.hasPath(AUTH_SUCCESS_REDIRECT_PATH_CONFIG_PATH)) {
_authSuccessRedirectPath = configs.getString(AUTH_SUCCESS_REDIRECT_PATH_CONFIG_PATH);
}
return this;
}
public Builder from(String ssoSettingsJsonStr) {
try {
jsonNode = _objectMapper.readTree(ssoSettingsJsonStr);
} catch (Exception e) {
throw new RuntimeException(
String.format("Failed to parse ssoSettingsJsonStr %s into JSON", ssoSettingsJsonStr));
}
if (jsonNode.has(BASE_URL)) {
_authBaseUrl = jsonNode.get(BASE_URL).asText();
}
if (jsonNode.has(OIDC_ENABLED)) {
_oidcEnabled = jsonNode.get(OIDC_ENABLED).asBoolean();
}
return this;
}
public SsoConfigs build() {
return new SsoConfigs(this);
}
}
}

View File

@ -1,13 +1,49 @@
package auth.sso;
import auth.sso.oidc.OidcConfigs;
import auth.sso.oidc.OidcProvider;
import com.datahub.authentication.Authentication;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import play.mvc.Http;
/** Singleton class that stores & serves reference to a single {@link SsoProvider} if one exists. */
/**
* Singleton class that stores & serves reference to a single {@link SsoProvider} if one exists.
* TODO: Refactor SsoManager to only accept SsoConfigs when initialized. See SsoConfigs TODO as
* well.
*/
@Slf4j
public class SsoManager {
private SsoProvider<?> _provider; // Only one active provider at a time.
private final Authentication
_authentication; // Authentication used to fetch SSO settings from GMS
private final String _ssoSettingsRequestUrl; // SSO settings request URL.
private final CloseableHttpClient _httpClient; // HTTP client for making requests to GMS.
private com.typesafe.config.Config _configs;
public SsoManager() {}
public SsoManager(
com.typesafe.config.Config configs,
Authentication authentication,
String ssoSettingsRequestUrl,
CloseableHttpClient httpClient) {
_configs = configs;
_authentication = Objects.requireNonNull(authentication, "authentication cannot be null");
_ssoSettingsRequestUrl =
Objects.requireNonNull(ssoSettingsRequestUrl, "ssoSettingsRequestUrl cannot be null");
_httpClient = Objects.requireNonNull(httpClient, "httpClient cannot be null");
_provider = null;
}
/**
* Returns true if SSO is enabled, meaning a non-null {@link SsoProvider} has been provided to the
@ -16,6 +52,7 @@ public class SsoManager {
* @return true if SSO logic is enabled, false otherwise.
*/
public boolean isSsoEnabled() {
refreshSsoProvider();
return _provider != null;
}
@ -24,17 +61,138 @@ public class SsoManager {
*
* @param provider the new {@link SsoProvider} to be used during authentication.
*/
public void setSsoProvider(@Nonnull final SsoProvider<?> provider) {
public void setSsoProvider(final SsoProvider<?> provider) {
_provider = provider;
}
public void setConfigs(final com.typesafe.config.Config configs) {
_configs = configs;
}
public void clearSsoProvider() {
_provider = null;
}
/**
* Gets the active {@link SsoProvider} instance.
*
* @return the {@SsoProvider} that should be used during authentication and on IdP callback, or
* null if SSO is not enabled.
*/
@Nullable
public SsoProvider<?> getSsoProvider() {
return _provider;
}
public void initializeSsoProvider() {
SsoConfigs ssoConfigs = null;
try {
ssoConfigs = new SsoConfigs.Builder().from(_configs).build();
} catch (Exception e) {
// Debug-level logging since this is expected to fail if SSO has not been configured.
log.debug(String.format("Missing SSO settings in static configs %s", _configs), e);
}
if (ssoConfigs != null && ssoConfigs.isOidcEnabled()) {
try {
OidcConfigs oidcConfigs = new OidcConfigs.Builder().from(_configs).build();
maybeUpdateOidcProvider(oidcConfigs);
} catch (Exception e) {
// Error-level logging since this is unexpected to fail if SSO has been configured.
log.error(String.format("Error building OidcConfigs from static configs %s", _configs), e);
}
} else {
// Clear the SSO Provider since no SSO is enabled.
clearSsoProvider();
}
refreshSsoProvider();
}
private void refreshSsoProvider() {
final Optional<String> maybeSsoSettingsJsonStr = getDynamicSsoSettings();
if (maybeSsoSettingsJsonStr.isEmpty()) {
return;
}
// If we receive a non-empty response, try to update the SSO provider.
final String ssoSettingsJsonStr = maybeSsoSettingsJsonStr.get();
SsoConfigs ssoConfigs;
try {
ssoConfigs = new SsoConfigs.Builder().from(ssoSettingsJsonStr).build();
} catch (Exception e) {
log.error(
String.format(
"Error building SsoConfigs from invalid json %s, reusing previous settings",
ssoSettingsJsonStr),
e);
return;
}
if (ssoConfigs != null && ssoConfigs.isOidcEnabled()) {
try {
OidcConfigs oidcConfigs =
new OidcConfigs.Builder().from(_configs, ssoSettingsJsonStr).build();
maybeUpdateOidcProvider(oidcConfigs);
} catch (Exception e) {
log.error(
String.format(
"Error building OidcConfigs from invalid json %s, reusing previous settings",
ssoSettingsJsonStr),
e);
}
} else {
// Clear the SSO Provider since no SSO is enabled.
clearSsoProvider();
}
}
private void maybeUpdateOidcProvider(OidcConfigs oidcConfigs) {
SsoProvider existingSsoProvider = getSsoProvider();
if (existingSsoProvider instanceof OidcProvider) {
OidcProvider existingOidcProvider = (OidcProvider) existingSsoProvider;
// If the existing provider is an OIDC provider and the configs are the same, do nothing.
if (existingOidcProvider.configs().equals(oidcConfigs)) {
return;
}
}
OidcProvider oidcProvider = new OidcProvider(oidcConfigs);
setSsoProvider(oidcProvider);
}
/** Call the Auth Service to get SSO settings */
@Nonnull
private Optional<String> getDynamicSsoSettings() {
CloseableHttpResponse response = null;
try {
final HttpPost request = new HttpPost(_ssoSettingsRequestUrl);
// Build JSON request to verify credentials for a native user.
request.setEntity(new StringEntity(""));
// Add authorization header with DataHub frontend system id and secret.
request.addHeader(Http.HeaderNames.AUTHORIZATION, _authentication.getCredentials());
response = _httpClient.execute(request);
final HttpEntity entity = response.getEntity();
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entity != null) {
// Successfully received the SSO settings
return Optional.of(EntityUtils.toString(entity));
} else {
log.debug("No SSO settings received from Auth Service, reusing previous settings");
}
} catch (Exception e) {
log.warn("Failed to get SSO settings due to exception, reusing previous settings", e);
} finally {
try {
if (response != null) {
response.close();
}
} catch (Exception e) {
log.warn("Failed to close http response", e);
}
}
return Optional.empty();
}
}

View File

@ -1,8 +1,10 @@
package auth.sso.oidc;
import static auth.AuthUtils.*;
import static auth.ConfigUtil.*;
import auth.sso.SsoConfigs;
import java.util.Objects;
import java.util.Optional;
import lombok.Getter;
@ -44,79 +46,204 @@ public class OidcConfigs extends SsoConfigs {
private static final String DEFAULT_OIDC_USERNAME_CLAIM = "email";
private static final String DEFAULT_OIDC_USERNAME_CLAIM_REGEX = "(.*)";
private static final String DEFAULT_OIDC_SCOPE =
"openid profile email"; // Often "group" must be included for groups.
private static final String DEFAULT_OIDC_SCOPE = "openid profile email";
// Often "group" must be included for groups.
private static final String DEFAULT_OIDC_CLIENT_NAME = "oidc";
private static final String DEFAULT_OIDC_CLIENT_AUTHENTICATION_METHOD = "client_secret_basic";
private static final String DEFAULT_OIDC_JIT_PROVISIONING_ENABLED = "true";
private static final String DEFAULT_OIDC_PRE_PROVISIONING_REQUIRED = "false";
private static final String DEFAULT_OIDC_EXTRACT_GROUPS_ENABLED =
"false"; // False since extraction of groups can overwrite existing group membership.
private static final String DEFAULT_OIDC_EXTRACT_GROUPS_ENABLED = "false";
// False since extraction of groups can overwrite existing group membership.
private static final String DEFAULT_OIDC_GROUPS_CLAIM = "groups";
private static final String DEFAULT_OIDC_READ_TIMEOUT = "5000";
private String clientId;
private String clientSecret;
private String discoveryUri;
private String userNameClaim;
private String userNameClaimRegex;
private String scope;
private String clientName;
private String clientAuthenticationMethod;
private boolean jitProvisioningEnabled;
private boolean preProvisioningRequired;
private boolean extractGroupsEnabled;
private String groupsClaimName;
private Optional<String> responseType;
private Optional<String> responseMode;
private Optional<Boolean> useNonce;
private Optional<String> customParamResource;
private String readTimeout;
private Optional<Boolean> extractJwtAccessTokenClaims;
private final String clientId;
private final String clientSecret;
private final String discoveryUri;
private final String userNameClaim;
private final String userNameClaimRegex;
private final String scope;
private final String clientName;
private final String clientAuthenticationMethod;
private final boolean jitProvisioningEnabled;
private final boolean preProvisioningRequired;
private final boolean extractGroupsEnabled;
private final String groupsClaimName;
private final Optional<String> responseType;
private final Optional<String> responseMode;
private final Optional<Boolean> useNonce;
private final Optional<String> customParamResource;
private final String readTimeout;
private final Optional<Boolean> extractJwtAccessTokenClaims;
private Optional<String> preferredJwsAlgorithm;
public OidcConfigs(final com.typesafe.config.Config configs) {
super(configs);
clientId = getRequired(configs, OIDC_CLIENT_ID_CONFIG_PATH);
clientSecret = getRequired(configs, OIDC_CLIENT_SECRET_CONFIG_PATH);
discoveryUri = getRequired(configs, OIDC_DISCOVERY_URI_CONFIG_PATH);
userNameClaim =
getOptional(configs, OIDC_USERNAME_CLAIM_CONFIG_PATH, DEFAULT_OIDC_USERNAME_CLAIM);
userNameClaimRegex =
getOptional(
configs, OIDC_USERNAME_CLAIM_REGEX_CONFIG_PATH, DEFAULT_OIDC_USERNAME_CLAIM_REGEX);
scope = getOptional(configs, OIDC_SCOPE_CONFIG_PATH, DEFAULT_OIDC_SCOPE);
clientName = getOptional(configs, OIDC_CLIENT_NAME_CONFIG_PATH, DEFAULT_OIDC_CLIENT_NAME);
clientAuthenticationMethod =
getOptional(
configs,
OIDC_CLIENT_AUTHENTICATION_METHOD_CONFIG_PATH,
DEFAULT_OIDC_CLIENT_AUTHENTICATION_METHOD);
jitProvisioningEnabled =
Boolean.parseBoolean(
getOptional(
configs,
OIDC_JIT_PROVISIONING_ENABLED_CONFIG_PATH,
DEFAULT_OIDC_JIT_PROVISIONING_ENABLED));
preProvisioningRequired =
Boolean.parseBoolean(
getOptional(
configs,
OIDC_PRE_PROVISIONING_REQUIRED_CONFIG_PATH,
DEFAULT_OIDC_PRE_PROVISIONING_REQUIRED));
extractGroupsEnabled =
Boolean.parseBoolean(
getOptional(configs, OIDC_EXTRACT_GROUPS_ENABLED, DEFAULT_OIDC_EXTRACT_GROUPS_ENABLED));
groupsClaimName =
getOptional(configs, OIDC_GROUPS_CLAIM_CONFIG_PATH_CONFIG_PATH, DEFAULT_OIDC_GROUPS_CLAIM);
responseType = getOptional(configs, OIDC_RESPONSE_TYPE);
responseMode = getOptional(configs, OIDC_RESPONSE_MODE);
useNonce = getOptional(configs, OIDC_USE_NONCE).map(Boolean::parseBoolean);
customParamResource = getOptional(configs, OIDC_CUSTOM_PARAM_RESOURCE);
readTimeout = getOptional(configs, OIDC_READ_TIMEOUT, DEFAULT_OIDC_READ_TIMEOUT);
extractJwtAccessTokenClaims =
getOptional(configs, OIDC_EXTRACT_JWT_ACCESS_TOKEN_CLAIMS).map(Boolean::parseBoolean);
preferredJwsAlgorithm =
Optional.ofNullable(getOptional(configs, OIDC_PREFERRED_JWS_ALGORITHM, null));
public OidcConfigs(Builder builder) {
super(builder);
this.clientId = builder.clientId;
this.clientSecret = builder.clientSecret;
this.discoveryUri = builder.discoveryUri;
this.userNameClaim = builder.userNameClaim;
this.userNameClaimRegex = builder.userNameClaimRegex;
this.scope = builder.scope;
this.clientName = builder.clientName;
this.clientAuthenticationMethod = builder.clientAuthenticationMethod;
this.jitProvisioningEnabled = builder.jitProvisioningEnabled;
this.preProvisioningRequired = builder.preProvisioningRequired;
this.extractGroupsEnabled = builder.extractGroupsEnabled;
this.groupsClaimName = builder.groupsClaimName;
this.responseType = builder.responseType;
this.responseMode = builder.responseMode;
this.useNonce = builder.useNonce;
this.customParamResource = builder.customParamResource;
this.readTimeout = builder.readTimeout;
this.extractJwtAccessTokenClaims = builder.extractJwtAccessTokenClaims;
this.preferredJwsAlgorithm = builder.preferredJwsAlgorithm;
}
public static class Builder extends SsoConfigs.Builder<Builder> {
private String clientId;
private String clientSecret;
private String discoveryUri;
private String userNameClaim = DEFAULT_OIDC_USERNAME_CLAIM;
private String userNameClaimRegex = DEFAULT_OIDC_USERNAME_CLAIM_REGEX;
private String scope = DEFAULT_OIDC_SCOPE;
private String clientName = DEFAULT_OIDC_CLIENT_NAME;
private String clientAuthenticationMethod = DEFAULT_OIDC_CLIENT_AUTHENTICATION_METHOD;
private boolean jitProvisioningEnabled =
Boolean.parseBoolean(DEFAULT_OIDC_JIT_PROVISIONING_ENABLED);
private boolean preProvisioningRequired =
Boolean.parseBoolean(DEFAULT_OIDC_PRE_PROVISIONING_REQUIRED);
private boolean extractGroupsEnabled =
Boolean.parseBoolean(DEFAULT_OIDC_EXTRACT_GROUPS_ENABLED);
private String groupsClaimName = DEFAULT_OIDC_GROUPS_CLAIM;
private Optional<String> responseType = Optional.empty();
private Optional<String> responseMode = Optional.empty();
private Optional<Boolean> useNonce = Optional.empty();
private Optional<String> customParamResource = Optional.empty();
private String readTimeout = DEFAULT_OIDC_READ_TIMEOUT;
private Optional<Boolean> extractJwtAccessTokenClaims = Optional.empty();
private Optional<String> preferredJwsAlgorithm = Optional.empty();
public Builder from(final com.typesafe.config.Config configs) {
super.from(configs);
clientId = getRequired(configs, OIDC_CLIENT_ID_CONFIG_PATH);
clientSecret = getRequired(configs, OIDC_CLIENT_SECRET_CONFIG_PATH);
discoveryUri = getRequired(configs, OIDC_DISCOVERY_URI_CONFIG_PATH);
userNameClaim =
getOptional(configs, OIDC_USERNAME_CLAIM_CONFIG_PATH, DEFAULT_OIDC_USERNAME_CLAIM);
userNameClaimRegex =
getOptional(
configs, OIDC_USERNAME_CLAIM_REGEX_CONFIG_PATH, DEFAULT_OIDC_USERNAME_CLAIM_REGEX);
scope = getOptional(configs, OIDC_SCOPE_CONFIG_PATH, DEFAULT_OIDC_SCOPE);
clientName = getOptional(configs, OIDC_CLIENT_NAME_CONFIG_PATH, DEFAULT_OIDC_CLIENT_NAME);
clientAuthenticationMethod =
getOptional(
configs,
OIDC_CLIENT_AUTHENTICATION_METHOD_CONFIG_PATH,
DEFAULT_OIDC_CLIENT_AUTHENTICATION_METHOD);
jitProvisioningEnabled =
Boolean.parseBoolean(
getOptional(
configs,
OIDC_JIT_PROVISIONING_ENABLED_CONFIG_PATH,
DEFAULT_OIDC_JIT_PROVISIONING_ENABLED));
preProvisioningRequired =
Boolean.parseBoolean(
getOptional(
configs,
OIDC_PRE_PROVISIONING_REQUIRED_CONFIG_PATH,
DEFAULT_OIDC_PRE_PROVISIONING_REQUIRED));
extractGroupsEnabled =
Boolean.parseBoolean(
getOptional(
configs, OIDC_EXTRACT_GROUPS_ENABLED, DEFAULT_OIDC_EXTRACT_GROUPS_ENABLED));
groupsClaimName =
getOptional(
configs, OIDC_GROUPS_CLAIM_CONFIG_PATH_CONFIG_PATH, DEFAULT_OIDC_GROUPS_CLAIM);
responseType = getOptional(configs, OIDC_RESPONSE_TYPE);
responseMode = getOptional(configs, OIDC_RESPONSE_MODE);
useNonce = getOptional(configs, OIDC_USE_NONCE).map(Boolean::parseBoolean);
customParamResource = getOptional(configs, OIDC_CUSTOM_PARAM_RESOURCE);
readTimeout = getOptional(configs, OIDC_READ_TIMEOUT, DEFAULT_OIDC_READ_TIMEOUT);
extractJwtAccessTokenClaims =
getOptional(configs, OIDC_EXTRACT_JWT_ACCESS_TOKEN_CLAIMS).map(Boolean::parseBoolean);
preferredJwsAlgorithm =
Optional.ofNullable(getOptional(configs, OIDC_PREFERRED_JWS_ALGORITHM, null));
return this;
}
public Builder from(final com.typesafe.config.Config configs, final String ssoSettingsJsonStr) {
super.from(ssoSettingsJsonStr);
if (jsonNode.has(CLIENT_ID)) {
clientId = jsonNode.get(CLIENT_ID).asText();
}
if (jsonNode.has(CLIENT_SECRET)) {
clientSecret = jsonNode.get(CLIENT_SECRET).asText();
}
if (jsonNode.has(DISCOVERY_URI)) {
discoveryUri = jsonNode.get(DISCOVERY_URI).asText();
}
if (jsonNode.has(USER_NAME_CLAIM)) {
userNameClaim = jsonNode.get(USER_NAME_CLAIM).asText();
}
if (jsonNode.has(USER_NAME_CLAIM_REGEX)) {
userNameClaimRegex = jsonNode.get(USER_NAME_CLAIM_REGEX).asText();
}
if (jsonNode.has(SCOPE)) {
scope = jsonNode.get(SCOPE).asText();
}
if (jsonNode.has(CLIENT_NAME)) {
clientName = jsonNode.get(CLIENT_NAME).asText();
}
if (jsonNode.has(CLIENT_AUTHENTICATION_METHOD)) {
clientAuthenticationMethod = jsonNode.get(CLIENT_AUTHENTICATION_METHOD).asText();
}
if (jsonNode.has(JIT_PROVISIONING_ENABLED)) {
jitProvisioningEnabled = jsonNode.get(JIT_PROVISIONING_ENABLED).asBoolean();
}
if (jsonNode.has(PRE_PROVISIONING_REQUIRED)) {
preProvisioningRequired = jsonNode.get(PRE_PROVISIONING_REQUIRED).asBoolean();
}
if (jsonNode.has(EXTRACT_GROUPS_ENABLED)) {
extractGroupsEnabled = jsonNode.get(EXTRACT_GROUPS_ENABLED).asBoolean();
}
if (jsonNode.has(GROUPS_CLAIM)) {
groupsClaimName = jsonNode.get(GROUPS_CLAIM).asText();
}
if (jsonNode.has(RESPONSE_TYPE)) {
responseType = Optional.of(jsonNode.get(RESPONSE_TYPE).asText());
}
if (jsonNode.has(RESPONSE_MODE)) {
responseMode = Optional.of(jsonNode.get(RESPONSE_MODE).asText());
}
if (jsonNode.has(USE_NONCE)) {
useNonce = Optional.of(jsonNode.get(USE_NONCE).asBoolean());
}
if (jsonNode.has(READ_TIMEOUT)) {
readTimeout = jsonNode.get(READ_TIMEOUT).asText();
}
if (jsonNode.has(EXTRACT_JWT_ACCESS_TOKEN_CLAIMS)) {
extractJwtAccessTokenClaims =
Optional.of(jsonNode.get(EXTRACT_JWT_ACCESS_TOKEN_CLAIMS).asBoolean());
}
if (jsonNode.has(OIDC_PREFERRED_JWS_ALGORITHM)) {
preferredJwsAlgorithm = Optional.of(jsonNode.get(OIDC_PREFERRED_JWS_ALGORITHM).asText());
} else {
preferredJwsAlgorithm =
Optional.ofNullable(getOptional(configs, OIDC_PREFERRED_JWS_ALGORITHM, null));
}
return this;
}
public OidcConfigs build() {
Objects.requireNonNull(_oidcEnabled, "oidcEnabled is required");
Objects.requireNonNull(clientId, "clientId is required");
Objects.requireNonNull(clientSecret, "clientSecret is required");
Objects.requireNonNull(discoveryUri, "discoveryUri is required");
Objects.requireNonNull(_authBaseUrl, "authBaseUrl is required");
return new OidcConfigs(this);
}
}
}

View File

@ -3,6 +3,7 @@ package client;
import com.datahub.authentication.Authentication;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.inject.Inject;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import javax.annotation.Nonnull;
@ -47,6 +48,7 @@ public class AuthServiceClient {
private final Authentication systemAuthentication;
private final CloseableHttpClient httpClient;
@Inject
public AuthServiceClient(
@Nonnull final String metadataServiceHost,
@Nonnull final Integer metadataServicePort,

View File

@ -9,11 +9,15 @@ import com.datahub.authentication.Authentication;
import com.linkedin.entity.client.SystemEntityClient;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.core.engine.CallbackLogic;
import org.pac4j.core.http.adapter.HttpActionAdapter;
@ -34,6 +38,7 @@ import play.mvc.Results;
public class SsoCallbackController extends CallbackController {
private final SsoManager _ssoManager;
private final Config _config;
@Inject
public SsoCallbackController(
@ -41,8 +46,10 @@ public class SsoCallbackController extends CallbackController {
@Nonnull Authentication systemAuthentication,
@Nonnull SystemEntityClient entityClient,
@Nonnull AuthServiceClient authClient,
@Nonnull Config config,
@Nonnull com.typesafe.config.Config configs) {
_ssoManager = ssoManager;
_config = config;
setDefaultUrl("/"); // By default, redirects to Home Page on log in.
setSaveInSession(false);
setCallbackLogic(
@ -126,7 +133,18 @@ public class SsoCallbackController extends CallbackController {
}
private boolean shouldHandleCallback(final String protocol) {
return _ssoManager.isSsoEnabled()
&& _ssoManager.getSsoProvider().protocol().getCommonName().equals(protocol);
if (!_ssoManager.isSsoEnabled()) {
return false;
}
updateConfig();
return _ssoManager.getSsoProvider().protocol().getCommonName().equals(protocol);
}
private void updateConfig() {
final Clients clients = new Clients();
final List<Client> clientList = new ArrayList<>();
clientList.add(_ssoManager.getSsoProvider().client());
clients.setClients(clientList);
_config.setClients(clients);
}
}

View File

@ -91,6 +91,9 @@ public class ApplicationTest extends WithBrowser {
@BeforeAll
public void init() throws IOException {
_gmsServer = new MockWebServer();
_gmsServer.enqueue(new MockResponse().setResponseCode(404)); // dynamic settings - not tested
_gmsServer.enqueue(new MockResponse().setResponseCode(404)); // dynamic settings - not tested
_gmsServer.enqueue(new MockResponse().setResponseCode(404)); // dynamic settings - not tested
_gmsServer.enqueue(new MockResponse().setBody(String.format("{\"value\":\"%s\"}", TEST_USER)));
_gmsServer.enqueue(
new MockResponse().setBody(String.format("{\"accessToken\":\"%s\"}", TEST_TOKEN)));

View File

@ -311,7 +311,9 @@ public class OidcConfigurationTest {
public void readTimeoutPropagation() {
CONFIG.withValue(OIDC_READ_TIMEOUT, ConfigValueFactory.fromAnyRef("10000"));
OidcConfigs oidcConfigs = new OidcConfigs(CONFIG);
OidcConfigs.Builder oidcConfigsBuilder = new OidcConfigs.Builder();
oidcConfigsBuilder.from(CONFIG);
OidcConfigs oidcConfigs = oidcConfigsBuilder.build();
OidcProvider oidcProvider = new OidcProvider(oidcConfigs);
assertEquals(10000, ((OidcClient) oidcProvider.client()).getConfiguration().getReadTimeout());
}

View File

@ -7,6 +7,11 @@ namespace com.linkedin.settings.global
"name": "globalSettingsInfo"
}
record GlobalSettingsInfo {
/**
* SSO integrations between DataHub and identity providers
*/
sso: optional SsoSettings
/**
* Settings related to the Views Feature
*/

View File

@ -0,0 +1,96 @@
namespace com.linkedin.settings.global
/**
* Settings for OIDC SSO integration.
*/
record OidcSettings {
/**
* Whether OIDC SSO is enabled.
*/
enabled: boolean
/**
* Unique client id issued by the identity provider.
*/
clientId: string
/**
* Unique client secret issued by the identity provider.
*/
clientSecret: string
/**
* The IdP OIDC discovery url.
*/
discoveryUri: string
/**
* ADVANCED. The attribute / claim used to derive the DataHub username. Defaults to "preferred_username".
*/
userNameClaim: optional string
/**
* ADVANCED. TThe regex used to parse the DataHub username from the user name claim. Defaults to (.*) (all).
*/
userNameClaimRegex: optional string
/**
* ADVANCED. String representing the requested scope from the IdP. Defaults to "oidc email profile".
*/
scope: optional string
/**
* ADVANCED. Which authentication method to use to pass credentials (clientId and clientSecret) to the token endpoint: Defaults to "client_secret_basic".
*/
clientAuthenticationMethod: optional string
/**
* ADVANCED. Whether DataHub users should be provisioned on login if they do not exist. Defaults to true.
*/
jitProvisioningEnabled: optional boolean
/**
* ADVANCED. Whether the user should already exist in DataHub on login, failing login if they are not. Defaults to false.
*/
preProvisioningRequired: optional boolean
/**
* ADVANCED. Whether groups should be extracted from a claim in the OIDC profile. Only applies if JIT provisioning is enabled. Groups will be created if they do not exist. Defaults to true.
*/
extractGroupsEnabled: optional boolean
/**
* ADVANCED. The OIDC claim to extract groups information from. Defaults to 'groups'.
*/
groupsClaim: optional string
/**
* ADVANCED. Response type.
*/
responseType: optional string
/**
* ADVANCED. Response mode.
*/
responseMode: optional string
/**
* ADVANCED. Use Nonce.
*/
useNonce: optional boolean
/**
* ADVANCED. Read timeout.
*/
readTimeout: optional long
/**
* ADVANCED. Whether to extract claims from JWT access token. Defaults to false.
*/
extractJwtAccessTokenClaims: optional boolean
/**
* ADVANCED. Which jws algorithm to use.
*/
preferredJwsAlgorithm: optional string
}

View File

@ -0,0 +1,16 @@
namespace com.linkedin.settings.global
/**
* SSO Integrations, supported on the UI.
*/
record SsoSettings {
/**
* Auth base URL.
*/
baseUrl: string
/**
* Optional OIDC SSO settings.
*/
oidcSettings: optional OidcSettings
}

View File

@ -0,0 +1,11 @@
namespace com.linkedin.settings.global
/**
* Enum to define SSO protocol type.
*/
enum SsoType {
/**
* OIDC SSO is used.
*/
OIDC
}

View File

@ -17,6 +17,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.linkedin.common.urn.CorpuserUrn;
import com.linkedin.common.urn.Urn;
import com.linkedin.gms.factory.config.ConfigurationProvider;
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.metadata.secret.SecretService;
import com.linkedin.settings.global.GlobalSettingsInfo;
import com.linkedin.settings.global.OidcSettings;
import com.linkedin.settings.global.SsoSettings;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
import javax.inject.Inject;
@ -48,6 +54,25 @@ public class AuthServiceController {
private static final String ARE_NATIVE_USER_CREDENTIALS_RESET_FIELD_NAME =
"areNativeUserCredentialsReset";
private static final String DOES_PASSWORD_MATCH_FIELD_NAME = "doesPasswordMatch";
private static final String BASE_URL = "baseUrl";
private static final String OIDC_ENABLED = "oidcEnabled";
private static final String CLIENT_ID = "clientId";
private static final String CLIENT_SECRET = "clientSecret";
private static final String DISCOVERY_URI = "discoveryUri";
private static final String USER_NAME_CLAIM = "userNameClaim";
private static final String USER_NAME_CLAIM_REGEX = "userNameClaimRegex";
private static final String SCOPE = "scope";
private static final String CLIENT_AUTHENTICATION_METHOD = "clientAuthenticationMethod";
private static final String JIT_PROVISIONING_ENABLED = "jitProvisioningEnabled";
private static final String PRE_PROVISIONING_REQUIRED = "preProvisioningRequired";
private static final String EXTRACT_GROUPS_ENABLED = "extractGroupsEnabled";
private static final String GROUPS_CLAIM = "groupsClaim";
private static final String RESPONSE_TYPE = "responseType";
private static final String RESPONSE_MODE = "responseMode";
private static final String USE_NONCE = "useNonce";
private static final String READ_TIMEOUT = "readTimeout";
private static final String EXTRACT_JWT_ACCESS_TOKEN_CLAIMS = "extractJwtAccessTokenClaims";
private static final String PREFERRED_JWS_ALGORITHM = "preferredJwsAlgorithm";
@Inject StatelessTokenService _statelessTokenService;
@ -59,6 +84,10 @@ public class AuthServiceController {
@Inject NativeUserService _nativeUserService;
@Inject EntityService _entityService;
@Inject SecretService _secretService;
@Inject InviteTokenService _inviteTokenService;
@Inject @Nullable TrackingService _trackingService;
@ -361,6 +390,41 @@ public class AuthServiceController {
});
}
/**
* Gets possible SSO settings.
*
* <p>Example Request:
*
* <p>POST /getSsoSettings -H "Authorization: Basic <system-client-id>:<system-client-secret>" {
* "userUrn": "urn:li:corpuser:test" "password": "password123" }
*
* <p>Example Response:
*
* <p>{ "clientId": "clientId", "clientSecret": "secret", "discoveryUri = "discoveryUri" }
*/
@PostMapping(value = "/getSsoSettings", produces = "application/json;charset=utf-8")
CompletableFuture<ResponseEntity<String>> getSsoSettings(final HttpEntity<String> httpEntity) {
return CompletableFuture.supplyAsync(
() -> {
try {
GlobalSettingsInfo globalSettingsInfo =
(GlobalSettingsInfo)
_entityService.getLatestAspect(
GLOBAL_SETTINGS_URN, GLOBAL_SETTINGS_INFO_ASPECT_NAME);
if (globalSettingsInfo == null || !globalSettingsInfo.hasSso()) {
log.debug("There are no SSO settings available");
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
SsoSettings ssoSettings =
Objects.requireNonNull(globalSettingsInfo.getSso(), "ssoSettings cannot be null");
String response = buildSsoSettingsResponse(ssoSettings);
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
});
}
// Currently, only internal system is authorized to generate a token on behalf of a user!
private boolean isAuthorizedToGenerateSessionToken(final String actorId) {
// Verify that the actor is an internal system caller.
@ -391,4 +455,67 @@ public class AuthServiceController {
json.put(DOES_PASSWORD_MATCH_FIELD_NAME, doesPasswordMatch);
return json.toString();
}
private String buildSsoSettingsResponse(final SsoSettings ssoSettings) {
String baseUrl = Objects.requireNonNull(ssoSettings.getBaseUrl());
JSONObject json = new JSONObject();
json.put(BASE_URL, baseUrl);
if (ssoSettings.hasOidcSettings()) {
OidcSettings oidcSettings =
Objects.requireNonNull(ssoSettings.getOidcSettings(), "oidcSettings cannot be null");
buildOidcSettingsResponse(json, oidcSettings);
}
return json.toString();
}
private void buildOidcSettingsResponse(JSONObject json, final OidcSettings oidcSettings) {
json.put(OIDC_ENABLED, oidcSettings.isEnabled());
json.put(CLIENT_ID, oidcSettings.getClientId());
json.put(CLIENT_SECRET, _secretService.decrypt(oidcSettings.getClientSecret()));
json.put(DISCOVERY_URI, oidcSettings.getDiscoveryUri());
if (oidcSettings.hasUserNameClaim()) {
json.put(USER_NAME_CLAIM, oidcSettings.getUserNameClaim());
}
if (oidcSettings.hasUserNameClaimRegex()) {
json.put(USER_NAME_CLAIM_REGEX, oidcSettings.getUserNameClaimRegex());
}
if (oidcSettings.hasScope()) {
json.put(SCOPE, oidcSettings.getScope());
}
if (oidcSettings.hasClientAuthenticationMethod()) {
json.put(CLIENT_AUTHENTICATION_METHOD, oidcSettings.getClientAuthenticationMethod());
}
if (oidcSettings.hasJitProvisioningEnabled()) {
json.put(JIT_PROVISIONING_ENABLED, oidcSettings.isJitProvisioningEnabled());
}
if (oidcSettings.hasPreProvisioningRequired()) {
json.put(PRE_PROVISIONING_REQUIRED, oidcSettings.isPreProvisioningRequired());
}
if (oidcSettings.hasExtractGroupsEnabled()) {
json.put(EXTRACT_GROUPS_ENABLED, oidcSettings.isExtractGroupsEnabled());
}
if (oidcSettings.hasGroupsClaim()) {
json.put(GROUPS_CLAIM, oidcSettings.getGroupsClaim());
}
if (oidcSettings.hasResponseType()) {
json.put(RESPONSE_TYPE, oidcSettings.getResponseType());
}
if (oidcSettings.hasResponseMode()) {
json.put(RESPONSE_MODE, oidcSettings.getResponseMode());
}
if (oidcSettings.hasUseNonce()) {
json.put(USE_NONCE, oidcSettings.isUseNonce());
}
if (oidcSettings.hasReadTimeout()) {
json.put(READ_TIMEOUT, oidcSettings.getReadTimeout());
}
if (oidcSettings.hasExtractJwtAccessTokenClaims()) {
json.put(EXTRACT_JWT_ACCESS_TOKEN_CLAIMS, oidcSettings.isExtractJwtAccessTokenClaims());
}
if (oidcSettings.hasPreferredJwsAlgorithm()) {
json.put(PREFERRED_JWS_ALGORITHM, oidcSettings.getPreferredJwsAlgorithm());
}
}
}