mirror of
https://github.com/bmlong137/alfresco-keycloak.git
synced 2025-05-12 21:24:43 +00:00
Test and deal with granular client scopes
This commit is contained in:
parent
5933acbb04
commit
cff32d017b
@ -162,6 +162,7 @@
|
||||
<property name="accessTokenService" ref="accessTokenService.impl" />
|
||||
<property name="userName" value="${keycloak.synchronization.user}" />
|
||||
<property name="password" value="${keycloak.synchronization.password}" />
|
||||
<property name="requiredClientScopes" value="${keycloak.synchronization.requiredClientScopes}" />
|
||||
</bean>
|
||||
|
||||
<bean id="userRegistry" class="${project.artifactId}.sync.KeycloakUserRegistry">
|
||||
|
@ -97,6 +97,7 @@ keycloak.roles.resourceMapper.default.prefix.property.prefix=ROLE_KEYCLOAK_${key
|
||||
keycloak.synchronization.enabled=true
|
||||
keycloak.synchronization.user=
|
||||
keycloak.synchronization.password=
|
||||
keycloak.synchronization.requiredClientScopes=
|
||||
keycloak.synchronization.personLoadBatchSize=50
|
||||
keycloak.synchronization.groupLoadBatchSize=50
|
||||
|
||||
|
@ -288,7 +288,7 @@ public class KeycloakAuthenticationComponent extends AbstractAuthenticationCompo
|
||||
String realUserName = userName;
|
||||
try
|
||||
{
|
||||
accessTokenHolder = this.accessTokenClient.obtainAccessToken(userName, new String(password));
|
||||
accessTokenHolder = this.accessTokenClient.obtainAccessToken(userName, new String(password), Collections.emptySet());
|
||||
realUserName = accessTokenHolder.getAccessToken().getPreferredUsername();
|
||||
|
||||
// for potential one-off authentication, we do not care particularly about the token TTL - so no validation here
|
||||
|
@ -21,6 +21,9 @@ import com.fasterxml.jackson.databind.MappingIterator;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -65,6 +68,8 @@ public class IDMClientImpl implements InitializingBean, IDMClient
|
||||
|
||||
protected String password;
|
||||
|
||||
protected final Collection<String> requiredClientScopes = new HashSet<>();
|
||||
|
||||
protected AccessTokenHolder accessToken;
|
||||
|
||||
/**
|
||||
@ -113,6 +118,19 @@ public class IDMClientImpl implements InitializingBean, IDMClient
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param requiredClientScopes
|
||||
* the requiredClientScopes to set
|
||||
*/
|
||||
public void setRequiredClientScopes(final String requiredClientScopes)
|
||||
{
|
||||
this.requiredClientScopes.clear();
|
||||
if (requiredClientScopes != null && !requiredClientScopes.isEmpty())
|
||||
{
|
||||
this.requiredClientScopes.addAll(Arrays.asList(requiredClientScopes.trim().split(" ")));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
@ -610,11 +628,12 @@ public class IDMClientImpl implements InitializingBean, IDMClient
|
||||
{
|
||||
if (this.userName != null && !this.userName.isEmpty())
|
||||
{
|
||||
this.accessToken = this.accessTokenService.obtainAccessToken(this.userName, this.password);
|
||||
this.accessToken = this.accessTokenService.obtainAccessToken(this.userName, this.password,
|
||||
this.requiredClientScopes);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.accessToken = this.accessTokenService.obtainAccessToken();
|
||||
this.accessToken = this.accessTokenService.obtainAccessToken(this.requiredClientScopes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package de.acosix.alfresco.keycloak.repo.token;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -57,17 +58,21 @@ public class AccessTokenClient
|
||||
* Obtains an access token for the service account of the client used to integrate this Alfresco isntance with Keycloak. This requires
|
||||
* that a service account has been enabled / configured in Keycloak.
|
||||
*
|
||||
* @param scopes
|
||||
* the optional scopes to request for the access token
|
||||
* @return the access token
|
||||
* @throws AccessTokenException
|
||||
* if the access token cannot be obtained
|
||||
*/
|
||||
public RefreshableAccessTokenHolder obtainAccessToken()
|
||||
public RefreshableAccessTokenHolder obtainAccessToken(final Collection<String> scopes)
|
||||
{
|
||||
LOGGER.debug("Obtaining client access token");
|
||||
ParameterCheck.mandatory("scopes", scopes);
|
||||
LOGGER.debug("Obtaining client access token with (optional) scopes {}", scopes);
|
||||
try
|
||||
{
|
||||
final AccessTokenResponse response = this.getAccessTokenImpl(formParams -> {
|
||||
formParams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
|
||||
this.processScopes(scopes, formParams);
|
||||
});
|
||||
final VerifiedTokens verifiedTokens = this.verifyAccessTokenResponse(response);
|
||||
final RefreshableAccessTokenHolder refreshableToken = new RefreshableAccessTokenHolder(response, verifiedTokens);
|
||||
@ -88,19 +93,26 @@ public class AccessTokenClient
|
||||
* the name of the user
|
||||
* @param password
|
||||
* the password provided by / for the user
|
||||
* @param scopes
|
||||
* the optional scopes to request for the access token
|
||||
* @return the access token
|
||||
* @throws AccessTokenException
|
||||
* if the access token cannot be obtained
|
||||
*/
|
||||
public RefreshableAccessTokenHolder obtainAccessToken(final String user, final String password)
|
||||
public RefreshableAccessTokenHolder obtainAccessToken(final String user, final String password, final Collection<String> scopes)
|
||||
{
|
||||
LOGGER.debug("Obtaining access token for user {}", user);
|
||||
ParameterCheck.mandatoryString("user", user);
|
||||
ParameterCheck.mandatoryString("password", password);
|
||||
ParameterCheck.mandatory("scopes", scopes);
|
||||
|
||||
LOGGER.debug("Obtaining access token for user {} with (optional) scopes {}", user, scopes);
|
||||
try
|
||||
{
|
||||
final AccessTokenResponse response = this.getAccessTokenImpl(formParams -> {
|
||||
formParams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
|
||||
formParams.add(new BasicNameValuePair("username", user));
|
||||
formParams.add(new BasicNameValuePair("password", password));
|
||||
this.processScopes(scopes, formParams);
|
||||
});
|
||||
final VerifiedTokens verifiedTokens = this.verifyAccessTokenResponse(response);
|
||||
final RefreshableAccessTokenHolder refreshableToken = new RefreshableAccessTokenHolder(response, verifiedTokens);
|
||||
@ -121,19 +133,26 @@ public class AccessTokenClient
|
||||
* the access token to exchange
|
||||
* @param client
|
||||
* the client / service for which to obtain an access token
|
||||
* @param scopes
|
||||
* the optional scopes to request for the access token
|
||||
* @return the access token to the requested client / service
|
||||
* @throws AccessTokenException
|
||||
* if the token cannot be exchanged
|
||||
*/
|
||||
public RefreshableAccessTokenHolder exchangeToken(final String accessToken, final String client)
|
||||
public RefreshableAccessTokenHolder exchangeToken(final String accessToken, final String client, final Collection<String> scopes)
|
||||
{
|
||||
LOGGER.debug("Exchanging {} for access token to client {}", accessToken, client);
|
||||
ParameterCheck.mandatoryString("accessToken", accessToken);
|
||||
ParameterCheck.mandatoryString("client", client);
|
||||
ParameterCheck.mandatory("scopes", scopes);
|
||||
|
||||
LOGGER.debug("Exchanging {} for access token to client {} with (optional) scopes {}", accessToken, client, scopes);
|
||||
try
|
||||
{
|
||||
final AccessTokenResponse response = this.getAccessTokenImpl(formParams -> {
|
||||
formParams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE));
|
||||
formParams.add(new BasicNameValuePair(OAuth2Constants.AUDIENCE, client));
|
||||
formParams.add(new BasicNameValuePair(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.REFRESH_TOKEN_TYPE));
|
||||
this.processScopes(scopes, formParams);
|
||||
});
|
||||
final VerifiedTokens verifiedTokens = this.verifyAccessTokenResponse(response, client);
|
||||
final RefreshableAccessTokenHolder refreshableToken = new RefreshableAccessTokenHolder(response, verifiedTokens);
|
||||
@ -178,6 +197,23 @@ public class AccessTokenClient
|
||||
}
|
||||
}
|
||||
|
||||
protected void processScopes(final Collection<String> scopes, final List<NameValuePair> formParams)
|
||||
{
|
||||
if (!scopes.isEmpty())
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder(scopes.size() * 16);
|
||||
for (final String scope : scopes)
|
||||
{
|
||||
if (sb.length() > 0)
|
||||
{
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(scope);
|
||||
}
|
||||
formParams.add(new BasicNameValuePair(OAuth2Constants.SCOPE, sb.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
protected VerifiedTokens verifyAccessTokenResponse(final AccessTokenResponse response)
|
||||
{
|
||||
final VerifiedTokens tokens;
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package de.acosix.alfresco.keycloak.repo.token;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Instances of this interface allow for the retrieval of access tokens in the Keycloak realm to which this Alfresco instance is connected.
|
||||
*
|
||||
@ -30,7 +33,21 @@ public interface AccessTokenService
|
||||
* @throws IllegalStateException
|
||||
* if no access token for the client can be obtained, e.g. if no service account for it has been configured in Keycloak
|
||||
*/
|
||||
AccessTokenHolder obtainAccessToken();
|
||||
default AccessTokenHolder obtainAccessToken()
|
||||
{
|
||||
return this.obtainAccessToken(Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a generic realm access token for the specific client of this Alfresco instance.
|
||||
*
|
||||
* @param scopes
|
||||
* the optional scopes to request for the access token
|
||||
* @return the holder for the access token
|
||||
* @throws IllegalStateException
|
||||
* if no access token for the client can be obtained, e.g. if no service account for it has been configured in Keycloak
|
||||
*/
|
||||
AccessTokenHolder obtainAccessToken(Collection<String> scopes);
|
||||
|
||||
/**
|
||||
* Obtains a generic realm access token for a specific user of the realm.
|
||||
@ -41,10 +58,27 @@ public interface AccessTokenService
|
||||
* the password of the user
|
||||
* @return the holder for the access token
|
||||
* @throws IllegalStateException
|
||||
* if no access token for the client can be obtained, e.g. if direct access grants have not been enabled for the specific
|
||||
* client of this Alfresco instance
|
||||
* if no access token for the user can be obtained
|
||||
*/
|
||||
AccessTokenHolder obtainAccessToken(String user, String password);
|
||||
default AccessTokenHolder obtainAccessToken(final String user, final String password)
|
||||
{
|
||||
return this.obtainAccessToken(user, password, Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a generic realm access token for a specific user of the realm.
|
||||
*
|
||||
* @param user
|
||||
* the name of the user
|
||||
* @param password
|
||||
* the password of the user
|
||||
* @param scopes
|
||||
* the optional scopes to request for the access token
|
||||
* @return the holder for the access token
|
||||
* @throws IllegalStateException
|
||||
* if no access token for the user can be obtained
|
||||
*/
|
||||
AccessTokenHolder obtainAccessToken(String user, String password, Collection<String> scopes);
|
||||
|
||||
/**
|
||||
* Performs a token exchange operation to obtain an access token for a specific client, acting in the name / context of the user for
|
||||
@ -56,7 +90,26 @@ public interface AccessTokenService
|
||||
* the client for which to obtain an access token
|
||||
* @return the holder for the access token
|
||||
* @throws IllegalStateException
|
||||
* if no access token for the client can be obtained, e.g. if no service account for it has been configured in Keycloak
|
||||
* if the access token cannot be exchanged for the client
|
||||
*/
|
||||
AccessTokenHolder exchangeToken(String accessToken, String client);
|
||||
default AccessTokenHolder exchangeToken(final String accessToken, final String client)
|
||||
{
|
||||
return this.exchangeToken(accessToken, client, Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a token exchange operation to obtain an access token for a specific client, acting in the name / context of the user for
|
||||
* which the original access token was issued.
|
||||
*
|
||||
* @param accessToken
|
||||
* the access token to exchange for token to another client
|
||||
* @param client
|
||||
* the client for which to obtain an access token
|
||||
* @param scopes
|
||||
* the optional scopes to request for the access token
|
||||
* @return the holder for the access token
|
||||
* @throws IllegalStateException
|
||||
* if the access token cannot be exchanged for the client
|
||||
*/
|
||||
AccessTokenHolder exchangeToken(String accessToken, String client, Collection<String> scopes);
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package de.acosix.alfresco.keycloak.repo.token;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.alfresco.util.ParameterCheck;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
@ -59,15 +61,16 @@ public class AccessTokenServiceImpl implements AccessTokenService, InitializingB
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AccessTokenHolder obtainAccessToken()
|
||||
public AccessTokenHolder obtainAccessToken(final Collection<String> scopes)
|
||||
{
|
||||
final RefreshableAccessTokenHolder refreshableToken = this.accessTokenClient.obtainAccessToken();
|
||||
ParameterCheck.mandatory("scopes", scopes);
|
||||
final RefreshableAccessTokenHolder refreshableToken = this.accessTokenClient.obtainAccessToken(scopes);
|
||||
|
||||
return new AccessTokenHolderImpl(refreshableToken, this.deployment.getTokenMinimumTimeToLive(),
|
||||
this.accessTokenClient::refreshAccessToken, () -> {
|
||||
try
|
||||
{
|
||||
return this.accessTokenClient.obtainAccessToken();
|
||||
return this.accessTokenClient.obtainAccessToken(scopes);
|
||||
}
|
||||
catch (final AccessTokenException atex)
|
||||
{
|
||||
@ -81,18 +84,18 @@ public class AccessTokenServiceImpl implements AccessTokenService, InitializingB
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AccessTokenHolder obtainAccessToken(final String user, final String password)
|
||||
public AccessTokenHolder obtainAccessToken(final String user, final String password, final Collection<String> scopes)
|
||||
{
|
||||
ParameterCheck.mandatoryString("user", user);
|
||||
ParameterCheck.mandatoryString("password", password);
|
||||
|
||||
final RefreshableAccessTokenHolder refreshableToken = this.accessTokenClient.obtainAccessToken(user, password);
|
||||
final RefreshableAccessTokenHolder refreshableToken = this.accessTokenClient.obtainAccessToken(user, password, scopes);
|
||||
|
||||
return new AccessTokenHolderImpl(refreshableToken, this.deployment.getTokenMinimumTimeToLive(),
|
||||
this.accessTokenClient::refreshAccessToken, () -> {
|
||||
try
|
||||
{
|
||||
return this.accessTokenClient.obtainAccessToken(user, password);
|
||||
return this.accessTokenClient.obtainAccessToken(user, password, scopes);
|
||||
}
|
||||
catch (final AccessTokenException atex)
|
||||
{
|
||||
@ -105,18 +108,18 @@ public class AccessTokenServiceImpl implements AccessTokenService, InitializingB
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AccessTokenHolder exchangeToken(final String accessToken, final String client)
|
||||
public AccessTokenHolder exchangeToken(final String accessToken, final String client, final Collection<String> scopes)
|
||||
{
|
||||
ParameterCheck.mandatoryString("accessToken", accessToken);
|
||||
ParameterCheck.mandatoryString("client", client);
|
||||
|
||||
final RefreshableAccessTokenHolder refreshableToken = this.accessTokenClient.exchangeToken(accessToken, client);
|
||||
final RefreshableAccessTokenHolder refreshableToken = this.accessTokenClient.exchangeToken(accessToken, client, scopes);
|
||||
|
||||
return new AccessTokenHolderImpl(refreshableToken, this.deployment.getTokenMinimumTimeToLive(), refreshToken -> {
|
||||
try
|
||||
{
|
||||
final String newAccessToken = this.accessTokenClient.refreshAccessToken(refreshToken).getToken();
|
||||
return this.accessTokenClient.exchangeToken(newAccessToken, client);
|
||||
return this.accessTokenClient.exchangeToken(newAccessToken, client, scopes);
|
||||
}
|
||||
catch (final AccessTokenException atex)
|
||||
{
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package de.acosix.alfresco.keycloak.repo.token;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* This no-op implementation class of an access token service may be used as a default implemenation in a subsystem proxy to avoid failing
|
||||
* if no Keycloak subsystem instance is active.
|
||||
@ -31,7 +33,7 @@ public class NoOpAccessTokenServiceImpl implements AccessTokenService
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AccessTokenHolder obtainAccessToken()
|
||||
public AccessTokenHolder obtainAccessToken(final Collection<String> scopes)
|
||||
{
|
||||
throw new AccessTokenUnsupportedException(UNSUPPORTED_MESSAGE);
|
||||
}
|
||||
@ -41,7 +43,7 @@ public class NoOpAccessTokenServiceImpl implements AccessTokenService
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AccessTokenHolder obtainAccessToken(final String user, final String password)
|
||||
public AccessTokenHolder obtainAccessToken(final String user, final String password, final Collection<String> scopes)
|
||||
{
|
||||
throw new AccessTokenUnsupportedException(UNSUPPORTED_MESSAGE);
|
||||
}
|
||||
@ -51,7 +53,7 @@ public class NoOpAccessTokenServiceImpl implements AccessTokenService
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AccessTokenHolder exchangeToken(final String accessToken, final String client)
|
||||
public AccessTokenHolder exchangeToken(final String accessToken, final String client, final Collection<String> scopes)
|
||||
{
|
||||
throw new AccessTokenUnsupportedException(UNSUPPORTED_MESSAGE);
|
||||
}
|
||||
|
@ -29,3 +29,5 @@ keycloak.adapter.directAuthHost=http://keycloak:8080
|
||||
|
||||
keycloak.synchronization.userFilter.containedInGroup.property.groupPaths=/Test A
|
||||
keycloak.synchronization.groupFilter.containedInGroup.property.groupPaths=/Test A
|
||||
|
||||
keycloak.synchronization.requiredClientScopes=realm-management
|
@ -10,18 +10,502 @@
|
||||
"en"
|
||||
],
|
||||
"defaultLocale": "en",
|
||||
"clientScopes": [
|
||||
{
|
||||
"name": "profile",
|
||||
"description": "OpenID Connect built-in scope: profile",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${profileScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "gender",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "gender",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "gender",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "full name",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-full-name-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "given name",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "firstName",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "given_name",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "picture",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "picture",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "picture",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "middle name",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "middleName",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "middle_name",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "website",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "website",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "website",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "birthdate",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "birthdate",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "birthdate",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "family name",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "lastName",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "family_name",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locale",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "locale",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "locale",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "zoneinfo",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "zoneinfo",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "zoneinfo",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "profile",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "profile",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "profile",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nickname",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "nickname",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "nickname",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updated at",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "updatedAt",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "updated_at",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "username",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "preferred_username",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"description": "OpenID Connect built-in scope: email",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${emailScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "email",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "email",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "email",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "email verified",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "emailVerified",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "email_verified",
|
||||
"jsonType.label": "boolean"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "address",
|
||||
"description": "OpenID Connect built-in scope: address",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${addressScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "address",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-address-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute.formatted": "formatted",
|
||||
"user.attribute.country": "country",
|
||||
"user.attribute.postal_code": "postal_code",
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute.street": "street",
|
||||
"id.token.claim": "true",
|
||||
"user.attribute.region": "region",
|
||||
"access.token.claim": "true",
|
||||
"user.attribute.locality": "locality"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "phone",
|
||||
"description": "OpenID Connect built-in scope: phone",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${phoneScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "phone number",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "phoneNumber",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "phone_number",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "phone number verified",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "phoneNumberVerified",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "phone_number_verified",
|
||||
"jsonType.label": "boolean"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "roles",
|
||||
"description": "OpenID Connect scope for add user roles to the access token",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${rolesScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "audience resolve",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-resolve-mapper",
|
||||
"consentRequired": false
|
||||
},
|
||||
{
|
||||
"name": "client roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.${client_id}.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realm roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-realm-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "realm_access.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "realm-roles",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "realm roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-realm-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "realm_access.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "realm-management",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "Realm Management Client Roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"multivalued": "true",
|
||||
"userinfo.token.claim": "false",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.realm-management.roles",
|
||||
"jsonType.label": "String",
|
||||
"usermodel.clientRoleMapping.clientId": "realm-management"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Realm Management Audience",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"included.client.audience": "realm-management",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "alfresco",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "Alfresco Client Roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"multivalued": "true",
|
||||
"userinfo.token.claim": "false",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.alfresco.roles",
|
||||
"jsonType.label": "String",
|
||||
"usermodel.clientRoleMapping.clientId": "alfresco"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Alfresco Audience",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"included.client.audience": "alfresco",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "web-origins",
|
||||
"description": "OpenID Connect scope for add allowed web origins to the access token",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
"consent.screen.text": ""
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "allowed web origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-allowed-origins-mapper",
|
||||
"consentRequired": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"defaultDefaultClientScopes": [
|
||||
"profile",
|
||||
"email",
|
||||
"roles",
|
||||
"web-origins"
|
||||
],
|
||||
"defaultOptionalClientScopes": [
|
||||
"address",
|
||||
"phone"
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"id": "alfresco",
|
||||
"clientId": "alfresco",
|
||||
"name": "Alfresco Repository",
|
||||
"baseUrl": "http://localhost:${docker.tests.repositoryPort}/alfresco",
|
||||
"rootUrl": "http://localhost:${docker.tests.repositoryPort}/alfresco",
|
||||
"adminUrl": "http://localhost:${docker.tests.repositoryPort}/alfresco/keycloak",
|
||||
"baseUrl": "/",
|
||||
"redirectUris": [
|
||||
"http://localhost:${docker.tests.repositoryPort}/alfresco/*"
|
||||
"/*"
|
||||
],
|
||||
"webOrigins": [
|
||||
"http://localhost:${docker.tests.repositoryPort}/alfresco"
|
||||
"/"
|
||||
],
|
||||
"enabled": true,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
@ -29,7 +513,18 @@
|
||||
"directAccessGrantsEnabled": true,
|
||||
"serviceAccountsEnabled": true,
|
||||
"publicClient": false,
|
||||
"protocol": "openid-connect"
|
||||
"protocol": "openid-connect",
|
||||
"defaultClientScopes": [
|
||||
"profile",
|
||||
"email",
|
||||
"address",
|
||||
"phone",
|
||||
"realm-roles",
|
||||
"alfresco"
|
||||
],
|
||||
"optionalClientScopes": [
|
||||
"realm-management"
|
||||
]
|
||||
}
|
||||
],
|
||||
"roles": {
|
||||
|
@ -20,6 +20,8 @@ import static org.alfresco.web.site.SlingshotPageView.REDIRECT_URI;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -27,6 +29,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@ -177,6 +180,37 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
|
||||
private static final ThreadLocal<String> LOGIN_REDIRECT_URL = new ThreadLocal<>();
|
||||
|
||||
private static final BiFunction<HttpServletRequest, HttpServletResponse, ServletRequestAttributes> SERVLET_REQUEST_ATTRIBUTES_FACTORY;
|
||||
|
||||
static
|
||||
{
|
||||
BiFunction<HttpServletRequest, HttpServletResponse, ServletRequestAttributes> factory;
|
||||
|
||||
try
|
||||
{
|
||||
// try and use the overloaded constructor available in newer versions
|
||||
final Constructor<ServletRequestAttributes> ctor = ServletRequestAttributes.class.getConstructor(HttpServletRequest.class,
|
||||
HttpServletResponse.class);
|
||||
factory = (req, res) -> {
|
||||
try
|
||||
{
|
||||
return ctor.newInstance(req, res);
|
||||
}
|
||||
catch (final InstantiationException | IllegalAccessException | InvocationTargetException e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to construct servlet request attributes", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (final NoSuchMethodException nsme)
|
||||
{
|
||||
// fallback to constructor that's available in all Share versions
|
||||
factory = (req, res) -> new ServletRequestAttributes(req);
|
||||
}
|
||||
|
||||
SERVLET_REQUEST_ATTRIBUTES_FACTORY = factory;
|
||||
}
|
||||
|
||||
protected ApplicationContext applicationContext;
|
||||
|
||||
protected DependencyInjectedFilter defaultSsoFilter;
|
||||
@ -265,28 +299,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
final RemoteConfigElement remoteConfig = (RemoteConfigElement) this.configService.getConfig("Remote").getConfigElement("remote");
|
||||
if (remoteConfig != null)
|
||||
{
|
||||
final EndpointDescriptor endpoint = remoteConfig.getEndpointDescriptor(this.primaryEndpoint);
|
||||
if (endpoint != null)
|
||||
{
|
||||
this.externalAuthEnabled = endpoint.getExternalAuth();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.error("Endpoint {} has not been defined in the application configuration", this.primaryEndpoint);
|
||||
}
|
||||
|
||||
if (this.secondaryEndpoints != null)
|
||||
{
|
||||
this.secondaryEndpoints = this.secondaryEndpoints.stream().filter(secondaryEndpoint -> {
|
||||
final boolean endpointExists = remoteConfig.getEndpointDescriptor(secondaryEndpoint) != null;
|
||||
if (!endpointExists)
|
||||
{
|
||||
LOGGER.info("Excluding configured secondary endpoint {} which is not defined in the application configuration",
|
||||
secondaryEndpoint);
|
||||
}
|
||||
return endpointExists;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
this.initFromRemoteEndpointConfig(remoteConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -297,37 +310,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
.getConfig(KeycloakConfigConstants.KEYCLOAK_CONFIG_SECTION_NAME).getConfigElement(KeycloakAdapterConfigElement.NAME);
|
||||
if (keycloakAdapterConfig != null)
|
||||
{
|
||||
final AdapterConfig adapterConfiguration = keycloakAdapterConfig.buildAdapterConfiguration();
|
||||
|
||||
// disable any CORS handling (if CORS is relevant, it should be handled by Share / Surf)
|
||||
adapterConfiguration.setCors(false);
|
||||
// BASIC authentication should never be used
|
||||
adapterConfiguration.setEnableBasicAuth(false);
|
||||
|
||||
this.keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfiguration);
|
||||
|
||||
// even in newer version than used by ACS 6.x does Keycloak lib not allow timeout configuration
|
||||
if (this.keycloakDeployment.getClient() != null)
|
||||
{
|
||||
final Long connectionTimeout = keycloakAdapterConfig.getConnectionTimeout();
|
||||
final Long socketTimeout = keycloakAdapterConfig.getSocketTimeout();
|
||||
|
||||
HttpClientBuilder httpClientBuilder = new HttpClientBuilder();
|
||||
if (connectionTimeout != null && connectionTimeout.longValue() >= 0)
|
||||
{
|
||||
httpClientBuilder = httpClientBuilder.establishConnectionTimeout(connectionTimeout.longValue(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
if (socketTimeout != null && socketTimeout.longValue() >= 0)
|
||||
{
|
||||
httpClientBuilder = httpClientBuilder.socketTimeout(socketTimeout.longValue(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
final HttpClient client = httpClientBuilder.build(adapterConfiguration);
|
||||
this.configureForcedRouteIfNecessary(keycloakAdapterConfig, client);
|
||||
this.keycloakDeployment.setClient(client);
|
||||
}
|
||||
|
||||
this.deploymentContext = new AdapterDeploymentContext(this.keycloakDeployment);
|
||||
this.initFromAdapterConfig(keycloakAdapterConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -439,8 +422,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
this.keycloakDeployment.getAuthServerBaseUrl());
|
||||
}
|
||||
|
||||
// TODO Figure out how to support Enteprise 6.2 / 7.x or 6.3+, which overload the constructor
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(req));
|
||||
RequestContextHolder.setRequestAttributes(SERVLET_REQUEST_ATTRIBUTES_FACTORY.apply(req, res));
|
||||
// Alfresco handling of RequestContext / ServletUtil / any other context holder is so immensely broken, it isn't even funny
|
||||
// this request context is for any handling that needs it until it gets nuked / bulldozed by RequestContextInterceptor
|
||||
// ...after which we will have to enhance that class' partially initialised context
|
||||
@ -501,6 +483,67 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
}
|
||||
}
|
||||
|
||||
protected void initFromRemoteEndpointConfig(final RemoteConfigElement remoteConfig)
|
||||
{
|
||||
final EndpointDescriptor endpoint = remoteConfig.getEndpointDescriptor(this.primaryEndpoint);
|
||||
if (endpoint != null)
|
||||
{
|
||||
this.externalAuthEnabled = endpoint.getExternalAuth();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.error("Endpoint {} has not been defined in the application configuration", this.primaryEndpoint);
|
||||
}
|
||||
|
||||
if (this.secondaryEndpoints != null)
|
||||
{
|
||||
this.secondaryEndpoints = this.secondaryEndpoints.stream().filter(secondaryEndpoint -> {
|
||||
final boolean endpointExists = remoteConfig.getEndpointDescriptor(secondaryEndpoint) != null;
|
||||
if (!endpointExists)
|
||||
{
|
||||
LOGGER.info("Excluding configured secondary endpoint {} which is not defined in the application configuration",
|
||||
secondaryEndpoint);
|
||||
}
|
||||
return endpointExists;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
protected void initFromAdapterConfig(final KeycloakAdapterConfigElement keycloakAdapterConfig)
|
||||
{
|
||||
final AdapterConfig adapterConfiguration = keycloakAdapterConfig.buildAdapterConfiguration();
|
||||
|
||||
// disable any CORS handling (if CORS is relevant, it should be handled by Share / Surf)
|
||||
adapterConfiguration.setCors(false);
|
||||
// BASIC authentication should never be used
|
||||
adapterConfiguration.setEnableBasicAuth(false);
|
||||
|
||||
this.keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfiguration);
|
||||
|
||||
// even in newer version than used by ACS 6.x does Keycloak lib not allow timeout configuration
|
||||
if (this.keycloakDeployment.getClient() != null)
|
||||
{
|
||||
final Long connectionTimeout = keycloakAdapterConfig.getConnectionTimeout();
|
||||
final Long socketTimeout = keycloakAdapterConfig.getSocketTimeout();
|
||||
|
||||
HttpClientBuilder httpClientBuilder = new HttpClientBuilder();
|
||||
if (connectionTimeout != null && connectionTimeout.longValue() >= 0)
|
||||
{
|
||||
httpClientBuilder = httpClientBuilder.establishConnectionTimeout(connectionTimeout.longValue(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
if (socketTimeout != null && socketTimeout.longValue() >= 0)
|
||||
{
|
||||
httpClientBuilder = httpClientBuilder.socketTimeout(socketTimeout.longValue(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
final HttpClient client = httpClientBuilder.build(adapterConfiguration);
|
||||
this.configureForcedRouteIfNecessary(keycloakAdapterConfig, client);
|
||||
this.keycloakDeployment.setClient(client);
|
||||
}
|
||||
|
||||
this.deploymentContext = new AdapterDeploymentContext(this.keycloakDeployment);
|
||||
}
|
||||
|
||||
protected void processLogout(final ServletContext context, final HttpServletRequest req, final HttpServletResponse res,
|
||||
final FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
authentication.chain=keycloak1:keycloak,alfrescoNtlm1:alfrescoNtlm
|
||||
|
||||
keycloak.adapter.auth-server-url=http://${docker.tests.host.name}:${docker.tests.keycloakPort}/auth
|
||||
keycloak.adapter.auth-server-url=http://localhost:${docker.tests.keycloakPort}/auth
|
||||
keycloak.adapter.realm=test
|
||||
keycloak.adapter.resource=alfresco
|
||||
keycloak.adapter.credentials.provider=secret
|
||||
@ -29,3 +29,5 @@ keycloak.adapter.directAuthHost=http://keycloak:8080
|
||||
|
||||
keycloak.synchronization.userFilter.containedInGroup.property.groupPaths=/Test A
|
||||
keycloak.synchronization.groupFilter.containedInGroup.property.groupPaths=/Test A
|
||||
|
||||
keycloak.synchronization.requiredClientScopes=realm-management
|
@ -10,18 +10,552 @@
|
||||
"en"
|
||||
],
|
||||
"defaultLocale": "en",
|
||||
"clientScopes": [
|
||||
{
|
||||
"name": "profile",
|
||||
"description": "OpenID Connect built-in scope: profile",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${profileScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "gender",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "gender",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "gender",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "full name",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-full-name-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "given name",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "firstName",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "given_name",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "picture",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "picture",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "picture",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "middle name",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "middleName",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "middle_name",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "website",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "website",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "website",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "birthdate",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "birthdate",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "birthdate",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "family name",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "lastName",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "family_name",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locale",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "locale",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "locale",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "zoneinfo",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "zoneinfo",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "zoneinfo",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "profile",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "profile",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "profile",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nickname",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "nickname",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "nickname",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updated at",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "updatedAt",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "updated_at",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "username",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "preferred_username",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"description": "OpenID Connect built-in scope: email",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${emailScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "email",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "email",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "email",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "email verified",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "emailVerified",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "email_verified",
|
||||
"jsonType.label": "boolean"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "address",
|
||||
"description": "OpenID Connect built-in scope: address",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${addressScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "address",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-address-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute.formatted": "formatted",
|
||||
"user.attribute.country": "country",
|
||||
"user.attribute.postal_code": "postal_code",
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute.street": "street",
|
||||
"id.token.claim": "true",
|
||||
"user.attribute.region": "region",
|
||||
"access.token.claim": "true",
|
||||
"user.attribute.locality": "locality"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "phone",
|
||||
"description": "OpenID Connect built-in scope: phone",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${phoneScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "phone number",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "phoneNumber",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "phone_number",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "phone number verified",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "phoneNumberVerified",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "phone_number_verified",
|
||||
"jsonType.label": "boolean"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "roles",
|
||||
"description": "OpenID Connect scope for add user roles to the access token",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "${rolesScopeConsentText}"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "audience resolve",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-resolve-mapper",
|
||||
"consentRequired": false
|
||||
},
|
||||
{
|
||||
"name": "client roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.${client_id}.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realm roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-realm-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "realm_access.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "realm-roles",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "realm roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-realm-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "realm_access.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "realm-management",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "Realm Management Client Roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"multivalued": "true",
|
||||
"userinfo.token.claim": "false",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.realm-management.roles",
|
||||
"jsonType.label": "String",
|
||||
"usermodel.clientRoleMapping.clientId": "realm-management"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Realm Management Audience",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"included.client.audience": "realm-management",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "alfresco",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "Alfresco Client Roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"multivalued": "true",
|
||||
"userinfo.token.claim": "false",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.alfresco.roles",
|
||||
"jsonType.label": "String",
|
||||
"usermodel.clientRoleMapping.clientId": "alfresco"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Alfresco Audience",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"included.client.audience": "alfresco",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "alfresco-share",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "Alfresco Share Client Roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"multivalued": "true",
|
||||
"userinfo.token.claim": "false",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.alfresco-share.roles",
|
||||
"jsonType.label": "String",
|
||||
"usermodel.clientRoleMapping.clientId": "alfresco-share"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Alfresco Share Audience",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"included.client.audience": "alfresco-share",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-property-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "username",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "preferred_username",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "web-origins",
|
||||
"description": "OpenID Connect scope for add allowed web origins to the access token",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
"consent.screen.text": ""
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "allowed web origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-allowed-origins-mapper",
|
||||
"consentRequired": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"defaultDefaultClientScopes": [
|
||||
"profile",
|
||||
"email",
|
||||
"roles",
|
||||
"web-origins"
|
||||
],
|
||||
"defaultOptionalClientScopes": [
|
||||
"address",
|
||||
"phone"
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"id": "alfresco",
|
||||
"clientId": "alfresco",
|
||||
"name": "Alfresco Repository",
|
||||
"baseUrl": "http://localhost:${docker.tests.repositoryPort}/alfresco",
|
||||
"rootUrl": "http://localhost:${docker.tests.repositoryPort}/alfresco",
|
||||
"adminUrl": "http://localhost:${docker.tests.repositoryPort}/alfresco/keycloak",
|
||||
"baseUrl": "/",
|
||||
"redirectUris": [
|
||||
"http://localhost:${docker.tests.repositoryPort}/alfresco/*"
|
||||
"/*"
|
||||
],
|
||||
"webOrigins": [
|
||||
"http://localhost:${docker.tests.repositoryPort}/alfresco"
|
||||
"/"
|
||||
],
|
||||
"enabled": true,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
@ -29,25 +563,41 @@
|
||||
"directAccessGrantsEnabled": true,
|
||||
"serviceAccountsEnabled": true,
|
||||
"publicClient": false,
|
||||
"protocol": "openid-connect"
|
||||
"protocol": "openid-connect",
|
||||
"defaultClientScopes": [
|
||||
"profile",
|
||||
"email",
|
||||
"address",
|
||||
"phone",
|
||||
"realm-roles",
|
||||
"alfresco"
|
||||
],
|
||||
"optionalClientScopes": [
|
||||
"realm-management"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "alfresco-share",
|
||||
"clientId": "alfresco-share",
|
||||
"name": "Alfresco Share",
|
||||
"baseUrl": "http://localhost:${docker.tests.sharePort}/share",
|
||||
"rootUrl": "http://localhost:${docker.tests.sharePort}/share",
|
||||
"adminUrl": "http://localhost:${docker.tests.sharePort}/share/keycloak",
|
||||
"baseUrl": "/",
|
||||
"redirectUris": [
|
||||
"http://localhost:${docker.tests.sharePort}/share/*"
|
||||
"/*"
|
||||
],
|
||||
"webOrigins": [
|
||||
"http://localhost:${docker.tests.sharePort}/share"
|
||||
"/"
|
||||
],
|
||||
"enabled": true,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"secret": "a5b3e8bc-39cc-4ddd-8c8f-1c34e7a35975",
|
||||
"publicClient": false,
|
||||
"protocol": "openid-connect"
|
||||
"protocol": "openid-connect",
|
||||
"defaultClientScopes": [
|
||||
"realm-roles",
|
||||
"alfresco-share"
|
||||
]
|
||||
},
|
||||
{
|
||||
"clientId": "realm-management",
|
||||
@ -250,6 +800,8 @@
|
||||
"manage-account"
|
||||
],
|
||||
"realm-management": [
|
||||
"query-groups",
|
||||
"query-users",
|
||||
"view-users",
|
||||
"view-clients"
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user