KEYCLOAK-5490
This commit is contained in:
parent
b7af96aa4d
commit
affeadf4f3
24 changed files with 435 additions and 418 deletions
|
@ -94,7 +94,11 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response exchangeNotLinked(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token) {
|
public Response exchangeNotLinked(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token) {
|
||||||
return exchangeErrorResponse(uriInfo, authorizedClient, tokenUserSession, token, "invalid_target");
|
return exchangeErrorResponse(uriInfo, authorizedClient, tokenUserSession, token, "identity provider is not linked");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response exchangeNotLinkedNoStore(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token) {
|
||||||
|
return exchangeErrorResponse(uriInfo, authorizedClient, tokenUserSession, token, "identity provider is not linked, can only link to current user session");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response exchangeErrorResponse(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, AccessToken token, String reason) {
|
protected Response exchangeErrorResponse(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, AccessToken token, String reason) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import javax.ws.rs.core.UriInfo;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface TokenExchangeTo {
|
public interface ExchangeTokenToIdentityProviderToken {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param authorizedClient client requesting exchange
|
* @param authorizedClient client requesting exchange
|
||||||
|
@ -39,5 +39,5 @@ public interface TokenExchangeTo {
|
||||||
* @param params form parameters received for requested exchange
|
* @param params form parameters received for requested exchange
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Response exchangeTo(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params);
|
Response exchangeFromToken(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params);
|
||||||
}
|
}
|
|
@ -24,7 +24,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.broker.provider.TokenExchangeTo;
|
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -62,7 +62,7 @@ import java.util.regex.Pattern;
|
||||||
/**
|
/**
|
||||||
* @author Pedro Igor
|
* @author Pedro Igor
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityProviderConfig> extends AbstractIdentityProvider<C> implements TokenExchangeTo {
|
public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityProviderConfig> extends AbstractIdentityProvider<C> implements ExchangeTokenToIdentityProviderToken {
|
||||||
protected static final Logger logger = Logger.getLogger(AbstractOAuth2IdentityProvider.class);
|
protected static final Logger logger = Logger.getLogger(AbstractOAuth2IdentityProvider.class);
|
||||||
|
|
||||||
public static final String OAUTH2_GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
|
public static final String OAUTH2_GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
|
||||||
|
@ -148,7 +148,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response exchangeTo(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params) {
|
public Response exchangeFromToken(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params) {
|
||||||
String requestedType = params.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
|
String requestedType = params.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
|
||||||
if (requestedType != null && !requestedType.equals(OAuth2Constants.ACCESS_TOKEN_TYPE)) {
|
if (requestedType != null && !requestedType.equals(OAuth2Constants.ACCESS_TOKEN_TYPE)) {
|
||||||
return exchangeUnsupportedRequiredType();
|
return exchangeUnsupportedRequiredType();
|
||||||
|
@ -156,7 +156,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
if (!getConfig().isStoreToken()) {
|
if (!getConfig().isStoreToken()) {
|
||||||
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
|
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
|
||||||
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
|
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
|
||||||
return exchangeNotSupported();
|
return exchangeNotLinkedNoStore(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||||
}
|
}
|
||||||
return exchangeSessionToken(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
return exchangeSessionToken(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||||
} else {
|
} else {
|
||||||
|
@ -317,7 +317,9 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
BrokeredIdentityContext federatedIdentity = getFederatedIdentity(response);
|
BrokeredIdentityContext federatedIdentity = getFederatedIdentity(response);
|
||||||
|
|
||||||
if (getConfig().isStoreToken()) {
|
if (getConfig().isStoreToken()) {
|
||||||
federatedIdentity.setToken(response);
|
// make sure that token wasn't already set by getFederatedIdentity();
|
||||||
|
// want to be able to allow provider to set the token itself.
|
||||||
|
if (federatedIdentity.getToken() == null)federatedIdentity.setToken(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
federatedIdentity.setIdpConfig(getConfig());
|
federatedIdentity.setIdpConfig(getConfig());
|
||||||
|
|
|
@ -231,9 +231,10 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
return exchangeNotLinked(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
return exchangeNotLinked(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(model.getToken(), AccessTokenResponse.class);
|
String modelTokenString = model.getToken();
|
||||||
Long exp = (Long)tokenResponse.getOtherClaims().get(ACCESS_TOKEN_EXPIRATION);
|
AccessTokenResponse tokenResponse = JsonSerialization.readValue(modelTokenString, AccessTokenResponse.class);
|
||||||
if (exp != null && (long)exp < Time.currentTime()) {
|
Integer exp = (Integer)tokenResponse.getOtherClaims().get(ACCESS_TOKEN_EXPIRATION);
|
||||||
|
if (exp != null && exp < Time.currentTime()) {
|
||||||
if (tokenResponse.getRefreshToken() == null) {
|
if (tokenResponse.getRefreshToken() == null) {
|
||||||
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||||
}
|
}
|
||||||
|
@ -243,19 +244,20 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||||
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret()).asString();
|
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret()).asString();
|
||||||
if (response.contains("error")) {
|
if (response.contains("error")) {
|
||||||
|
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
|
||||||
model.setToken(null);
|
model.setToken(null);
|
||||||
session.users().updateFederatedIdentity(authorizedClient.getRealm(), tokenSubject, model);
|
session.users().updateFederatedIdentity(authorizedClient.getRealm(), tokenSubject, model);
|
||||||
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||||
}
|
}
|
||||||
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
||||||
if (newResponse.getExpiresIn() > 0) {
|
if (newResponse.getExpiresIn() > 0) {
|
||||||
long accessTokenExpiration = Time.currentTime() + newResponse.getExpiresIn();
|
int accessTokenExpiration = Time.currentTime() + (int)newResponse.getExpiresIn();
|
||||||
newResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
|
newResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
|
||||||
response = JsonSerialization.writeValueAsString(newResponse);
|
response = JsonSerialization.writeValueAsString(newResponse);
|
||||||
}
|
}
|
||||||
String oldToken = tokenUserSession.getNote(FEDERATED_ACCESS_TOKEN);
|
String oldToken = tokenUserSession.getNote(FEDERATED_ACCESS_TOKEN);
|
||||||
if (oldToken != null && oldToken.equals(tokenResponse.getToken())) {
|
if (oldToken != null && oldToken.equals(tokenResponse.getToken())) {
|
||||||
long accessTokenExpiration = newResponse.getExpiresIn() > 0 ? Time.currentTime() + newResponse.getExpiresIn() : 0;
|
int accessTokenExpiration = newResponse.getExpiresIn() > 0 ? Time.currentTime() + (int)newResponse.getExpiresIn() : 0;
|
||||||
tokenUserSession.setNote(FEDERATED_TOKEN_EXPIRATION, Long.toString(accessTokenExpiration));
|
tokenUserSession.setNote(FEDERATED_TOKEN_EXPIRATION, Long.toString(accessTokenExpiration));
|
||||||
tokenUserSession.setNote(FEDERATED_REFRESH_TOKEN, newResponse.getRefreshToken());
|
tokenUserSession.setNote(FEDERATED_REFRESH_TOKEN, newResponse.getRefreshToken());
|
||||||
tokenUserSession.setNote(FEDERATED_ACCESS_TOKEN, newResponse.getToken());
|
tokenUserSession.setNote(FEDERATED_ACCESS_TOKEN, newResponse.getToken());
|
||||||
|
@ -302,6 +304,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||||
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret()).asString();
|
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret()).asString();
|
||||||
if (response.contains("error")) {
|
if (response.contains("error")) {
|
||||||
|
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
|
||||||
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||||
}
|
}
|
||||||
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
||||||
|
@ -341,13 +344,11 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
BrokeredIdentityContext identity = extractIdentity(tokenResponse, accessToken, idToken);
|
BrokeredIdentityContext identity = extractIdentity(tokenResponse, accessToken, idToken);
|
||||||
|
|
||||||
if (getConfig().isStoreToken()) {
|
if (getConfig().isStoreToken()) {
|
||||||
String response1 = response;
|
|
||||||
if (tokenResponse.getExpiresIn() > 0) {
|
if (tokenResponse.getExpiresIn() > 0) {
|
||||||
long accessTokenExpiration = Time.currentTime() + tokenResponse.getExpiresIn();
|
long accessTokenExpiration = Time.currentTime() + tokenResponse.getExpiresIn();
|
||||||
tokenResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
|
tokenResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
|
||||||
response1 = JsonSerialization.writeValueAsString(tokenResponse);
|
response = JsonSerialization.writeValueAsString(tokenResponse);
|
||||||
}
|
}
|
||||||
response = response1;
|
|
||||||
identity.setToken(response);
|
identity.setToken(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.broker.provider.IdentityProvider;
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
import org.keycloak.broker.provider.TokenExchangeTo;
|
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
@ -39,7 +39,6 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
@ -69,7 +68,6 @@ import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -587,7 +585,7 @@ public class TokenEndpoint {
|
||||||
String requestedIssuer = formParams.getFirst(OAuth2Constants.REQUESTED_ISSUER);
|
String requestedIssuer = formParams.getFirst(OAuth2Constants.REQUESTED_ISSUER);
|
||||||
|
|
||||||
if (requestedIssuer == null) {
|
if (requestedIssuer == null) {
|
||||||
return exchangeClientToClient(authResult);
|
return exchangeClientToClient(authResult.getUser(), authResult.getSession());
|
||||||
} else {
|
} else {
|
||||||
return exchangeToIdentityProvider(authResult, requestedIssuer);
|
return exchangeToIdentityProvider(authResult, requestedIssuer);
|
||||||
}
|
}
|
||||||
|
@ -601,7 +599,7 @@ public class TokenEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
IdentityProvider provider = IdentityBrokerService.getIdentityProvider(session, realm, requestedIssuer);
|
IdentityProvider provider = IdentityBrokerService.getIdentityProvider(session, realm, requestedIssuer);
|
||||||
if (!(provider instanceof TokenExchangeTo)) {
|
if (!(provider instanceof ExchangeTokenToIdentityProviderToken)) {
|
||||||
event.error(Errors.UNKNOWN_IDENTITY_PROVIDER);
|
event.error(Errors.UNKNOWN_IDENTITY_PROVIDER);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Issuer does not support token exchange", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Issuer does not support token exchange", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
@ -610,12 +608,12 @@ public class TokenEndpoint {
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
|
throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
Response response = ((TokenExchangeTo)provider).exchangeTo(uriInfo, client, authResult.getSession(), authResult.getUser(), authResult.getToken(), formParams);
|
Response response = ((ExchangeTokenToIdentityProviderToken)provider).exchangeFromToken(uriInfo, client, authResult.getSession(), authResult.getUser(), authResult.getToken(), formParams);
|
||||||
return Cors.add(request, Response.fromResponse(response)).auth().allowedOrigins(uriInfo, client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
return Cors.add(request, Response.fromResponse(response)).auth().allowedOrigins(uriInfo, client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response exchangeClientToClient(AuthenticationManager.AuthResult subject) {
|
protected Response exchangeClientToClient(UserModel targetUser, UserSessionModel targetUserSession) {
|
||||||
String requestedTokenType = formParams.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
|
String requestedTokenType = formParams.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
|
||||||
if (requestedTokenType == null) {
|
if (requestedTokenType == null) {
|
||||||
requestedTokenType = OAuth2Constants.REFRESH_TOKEN_TYPE;
|
requestedTokenType = OAuth2Constants.REFRESH_TOKEN_TYPE;
|
||||||
|
@ -653,25 +651,19 @@ public class TokenEndpoint {
|
||||||
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
|
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
|
||||||
|
|
||||||
AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, targetClient, false);
|
AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, targetClient, false);
|
||||||
authSession.setAuthenticatedUser(subject.getUser());
|
authSession.setAuthenticatedUser(targetUser);
|
||||||
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
||||||
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
|
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
|
||||||
|
|
||||||
UserSessionModel userSession = subject.getSession();
|
event.session(targetUserSession);
|
||||||
event.session(userSession);
|
|
||||||
|
|
||||||
AuthenticationManager.setRolesAndMappersInSession(authSession);
|
AuthenticationManager.setRolesAndMappersInSession(authSession);
|
||||||
AuthenticatedClientSessionModel clientSession = TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
AuthenticatedClientSessionModel clientSession = TokenManager.attachAuthenticationSession(this.session, targetUserSession, authSession);
|
||||||
|
|
||||||
// Notes about client details
|
updateUserSessionFromClientAuth(targetUserSession);
|
||||||
userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
|
|
||||||
userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
|
|
||||||
userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteAddr());
|
|
||||||
|
|
||||||
updateUserSessionFromClientAuth(userSession);
|
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, this.session, targetUserSession, clientSession)
|
||||||
|
|
||||||
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, session, userSession, clientSession)
|
|
||||||
.generateAccessToken();
|
.generateAccessToken();
|
||||||
responseBuilder.getAccessToken().issuedFor(client.getClientId());
|
responseBuilder.getAccessToken().issuedFor(client.getClientId());
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.keycloak.models.ClientModel;
|
||||||
public interface AdminPermissionManagement {
|
public interface AdminPermissionManagement {
|
||||||
public static final String MANAGE_SCOPE = "manage";
|
public static final String MANAGE_SCOPE = "manage";
|
||||||
public static final String VIEW_SCOPE = "view";
|
public static final String VIEW_SCOPE = "view";
|
||||||
public static final String EXCHANGE_TO_SCOPE="exchange-to";
|
public static final String TOKEN_EXCHANGE ="token-exchange";
|
||||||
|
|
||||||
ClientModel getRealmManagementClient();
|
ClientModel getRealmManagementClient();
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.EXCHANGE_TO_SCOPE;
|
import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.TOKEN_EXCHANGE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages default policies for all users.
|
* Manages default policies for all users.
|
||||||
|
@ -87,7 +87,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getExchangeToPermissionName(ClientModel client) {
|
private String getExchangeToPermissionName(ClientModel client) {
|
||||||
return EXCHANGE_TO_SCOPE + ".permission.client." + client.getId();
|
return TOKEN_EXCHANGE + ".permission.client." + client.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize(ClientModel client) {
|
private void initialize(ClientModel client) {
|
||||||
|
@ -107,7 +107,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
||||||
Scope mapRoleClientScope = root.initializeScope(MAP_ROLES_CLIENT_SCOPE, server);
|
Scope mapRoleClientScope = root.initializeScope(MAP_ROLES_CLIENT_SCOPE, server);
|
||||||
Scope mapRoleCompositeScope = root.initializeScope(MAP_ROLES_COMPOSITE_SCOPE, server);
|
Scope mapRoleCompositeScope = root.initializeScope(MAP_ROLES_COMPOSITE_SCOPE, server);
|
||||||
Scope configureScope = root.initializeScope(CONFIGURE_SCOPE, server);
|
Scope configureScope = root.initializeScope(CONFIGURE_SCOPE, server);
|
||||||
Scope exchangeToScope = root.initializeScope(EXCHANGE_TO_SCOPE, server);
|
Scope exchangeToScope = root.initializeScope(TOKEN_EXCHANGE, server);
|
||||||
|
|
||||||
String resourceName = getResourceName(client);
|
String resourceName = getResourceName(client);
|
||||||
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
|
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
|
||||||
|
@ -207,7 +207,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
||||||
}
|
}
|
||||||
|
|
||||||
private Scope exchangeToScope(ResourceServer server) {
|
private Scope exchangeToScope(ResourceServer server) {
|
||||||
return authz.getStoreFactory().getScopeStore().findByName(EXCHANGE_TO_SCOPE, server.getId());
|
return authz.getStoreFactory().getScopeStore().findByName(TOKEN_EXCHANGE, server.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Scope configureScope(ResourceServer server) {
|
private Scope configureScope(ResourceServer server) {
|
||||||
|
@ -293,7 +293,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
||||||
scopes.put(MAP_ROLES_SCOPE, mapRolesPermission(client).getId());
|
scopes.put(MAP_ROLES_SCOPE, mapRolesPermission(client).getId());
|
||||||
scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId());
|
scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId());
|
||||||
scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId());
|
scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId());
|
||||||
scopes.put(EXCHANGE_TO_SCOPE, exchangeToPermission(client).getId());
|
scopes.put(TOKEN_EXCHANGE, exchangeToPermission(client).getId());
|
||||||
return scopes;
|
return scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +328,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
||||||
|
|
||||||
Scope scope = exchangeToScope(server);
|
Scope scope = exchangeToScope(server);
|
||||||
if (scope == null) {
|
if (scope == null) {
|
||||||
logger.debug(EXCHANGE_TO_SCOPE + " not initialized");
|
logger.debug(TOKEN_EXCHANGE + " not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient);
|
ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient);
|
||||||
|
|
|
@ -37,10 +37,10 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.EXCHANGE_TO_SCOPE;
|
import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.TOKEN_EXCHANGE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages default policies for all users.
|
* Manages default policies for identity providers.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -65,12 +65,12 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getExchangeToPermissionName(IdentityProviderModel idp) {
|
private String getExchangeToPermissionName(IdentityProviderModel idp) {
|
||||||
return EXCHANGE_TO_SCOPE + ".permission.idp." + idp.getInternalId();
|
return TOKEN_EXCHANGE + ".permission.idp." + idp.getInternalId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize(IdentityProviderModel idp) {
|
private void initialize(IdentityProviderModel idp) {
|
||||||
ResourceServer server = root.initializeRealmResourceServer();
|
ResourceServer server = root.initializeRealmResourceServer();
|
||||||
Scope exchangeToScope = root.initializeScope(EXCHANGE_TO_SCOPE, server);
|
Scope exchangeToScope = root.initializeScope(TOKEN_EXCHANGE, server);
|
||||||
|
|
||||||
String resourceName = getResourceName(idp);
|
String resourceName = getResourceName(idp);
|
||||||
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
|
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
|
||||||
|
@ -124,7 +124,7 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
|
||||||
|
|
||||||
|
|
||||||
private Scope exchangeToScope(ResourceServer server) {
|
private Scope exchangeToScope(ResourceServer server) {
|
||||||
return authz.getStoreFactory().getScopeStore().findByName(EXCHANGE_TO_SCOPE, server.getId());
|
return authz.getStoreFactory().getScopeStore().findByName(TOKEN_EXCHANGE, server.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -141,7 +141,7 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
|
||||||
public Map<String, String> getPermissions(IdentityProviderModel idp) {
|
public Map<String, String> getPermissions(IdentityProviderModel idp) {
|
||||||
initialize(idp);
|
initialize(idp);
|
||||||
Map<String, String> scopes = new LinkedHashMap<>();
|
Map<String, String> scopes = new LinkedHashMap<>();
|
||||||
scopes.put(EXCHANGE_TO_SCOPE, exchangeToPermission(idp).getId());
|
scopes.put(TOKEN_EXCHANGE, exchangeToPermission(idp).getId());
|
||||||
return scopes;
|
return scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
|
||||||
|
|
||||||
Scope scope = exchangeToScope(server);
|
Scope scope = exchangeToScope(server);
|
||||||
if (scope == null) {
|
if (scope == null) {
|
||||||
logger.debug(EXCHANGE_TO_SCOPE + " not initialized");
|
logger.debug(TOKEN_EXCHANGE + " not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient);
|
ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient);
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.broker.provider.TokenExchangeTo;
|
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
|
||||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -61,7 +61,7 @@ import java.net.URI;
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2IdentityProviderConfig> implements
|
public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2IdentityProviderConfig> implements
|
||||||
SocialIdentityProvider<OAuth2IdentityProviderConfig>, TokenExchangeTo {
|
SocialIdentityProvider<OAuth2IdentityProviderConfig>, ExchangeTokenToIdentityProviderToken {
|
||||||
|
|
||||||
String TWITTER_TOKEN_TYPE="twitter";
|
String TWITTER_TOKEN_TYPE="twitter";
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response exchangeTo(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, org.keycloak.representations.AccessToken token, MultivaluedMap<String, String> params) {
|
public Response exchangeFromToken(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, org.keycloak.representations.AccessToken token, MultivaluedMap<String, String> params) {
|
||||||
String requestedType = params.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
|
String requestedType = params.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
|
||||||
if (requestedType != null && !requestedType.equals(TWITTER_TOKEN_TYPE)) {
|
if (requestedType != null && !requestedType.equals(TWITTER_TOKEN_TYPE)) {
|
||||||
return exchangeUnsupportedRequiredType();
|
return exchangeUnsupportedRequiredType();
|
||||||
|
@ -111,7 +111,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
||||||
if (!getConfig().isStoreToken()) {
|
if (!getConfig().isStoreToken()) {
|
||||||
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
|
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
|
||||||
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
|
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
|
||||||
return exchangeNotSupported();
|
return exchangeNotLinkedNoStore(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||||
}
|
}
|
||||||
return exchangeSessionToken(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
return exchangeSessionToken(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -47,7 +47,7 @@ import java.util.Map;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@WebServlet("/client-linking")
|
@WebServlet("/exchange-linking")
|
||||||
public class LinkAndExchangeServlet extends HttpServlet {
|
public class LinkAndExchangeServlet extends HttpServlet {
|
||||||
|
|
||||||
private String getPostDataString(HashMap<String, String> params) throws UnsupportedEncodingException{
|
private String getPostDataString(HashMap<String, String> params) throws UnsupportedEncodingException{
|
||||||
|
@ -100,9 +100,18 @@ public class LinkAndExchangeServlet extends HttpServlet {
|
||||||
writer.flush();
|
writer.flush();
|
||||||
writer.close();
|
writer.close();
|
||||||
os.close();
|
os.close();
|
||||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(conn.getInputStream(), AccessTokenResponse.class);
|
if (conn.getResponseCode() == 200) {
|
||||||
conn.getInputStream().close();
|
AccessTokenResponse tokenResponse = JsonSerialization.readValue(conn.getInputStream(), AccessTokenResponse.class);
|
||||||
return tokenResponse;
|
conn.getInputStream().close();
|
||||||
|
return tokenResponse;
|
||||||
|
} else if (conn.getResponseCode() == 400) {
|
||||||
|
AccessTokenResponse tokenResponse = JsonSerialization.readValue(conn.getErrorStream(), AccessTokenResponse.class);
|
||||||
|
conn.getErrorStream().close();
|
||||||
|
return tokenResponse;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unknown error!");
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +152,9 @@ public class LinkAndExchangeServlet extends HttpServlet {
|
||||||
|
|
||||||
String redirectUri = KeycloakUriBuilder.fromUri(request.getRequestURL().toString())
|
String redirectUri = KeycloakUriBuilder.fromUri(request.getRequestURL().toString())
|
||||||
.replaceQuery(null)
|
.replaceQuery(null)
|
||||||
.queryParam("response", "true").build().toString();
|
.queryParam("response", "true")
|
||||||
|
.queryParam("realm", realm)
|
||||||
|
.queryParam("provider", provider).build().toString();
|
||||||
String accountLinkUrl = KeycloakUriBuilder.fromUri(linkUrl)
|
String accountLinkUrl = KeycloakUriBuilder.fromUri(linkUrl)
|
||||||
.queryParam("redirect_uri", redirectUri).build().toString();
|
.queryParam("redirect_uri", redirectUri).build().toString();
|
||||||
resp.setStatus(302);
|
resp.setStatus(302);
|
||||||
|
@ -159,6 +170,24 @@ public class LinkAndExchangeServlet extends HttpServlet {
|
||||||
} else {
|
} else {
|
||||||
pw.println("Account Linked");
|
pw.println("Account Linked");
|
||||||
}
|
}
|
||||||
|
pw.println("trying exchange");
|
||||||
|
try {
|
||||||
|
String provider = request.getParameter("provider");
|
||||||
|
String realm = request.getParameter("realm");
|
||||||
|
KeycloakSecurityContext session = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
|
||||||
|
AccessToken token = session.getToken();
|
||||||
|
String clientId = token.getAudience()[0];
|
||||||
|
String tokenString = session.getTokenString();
|
||||||
|
AccessTokenResponse response = doTokenExchange(realm, tokenString, provider, clientId, "password");
|
||||||
|
error = (String)response.getOtherClaims().get("error");
|
||||||
|
if (error == null) {
|
||||||
|
if (response.getToken() != null) pw.println("Exchange token received");
|
||||||
|
} else {
|
||||||
|
pw.print("Error with exchange: " + error);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
pw.print("</body></html>");
|
pw.print("</body></html>");
|
||||||
pw.flush();
|
pw.flush();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -59,10 +59,16 @@ import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.WaitUtils;
|
import org.keycloak.testsuite.util.WaitUtils;
|
||||||
|
import org.keycloak.util.BasicAuthHelper;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.client.Client;
|
import javax.ws.rs.client.Client;
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
import javax.ws.rs.client.ClientBuilder;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.Form;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -98,7 +104,7 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
||||||
|
|
||||||
public static class ClientApp extends AbstractPageWithInjectedUrl {
|
public static class ClientApp extends AbstractPageWithInjectedUrl {
|
||||||
|
|
||||||
public static final String DEPLOYMENT_NAME = "client-linking";
|
public static final String DEPLOYMENT_NAME = "exchange-linking";
|
||||||
|
|
||||||
@ArquillianResource
|
@ArquillianResource
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
@OperateOnDeployment(DEPLOYMENT_NAME)
|
||||||
|
@ -124,9 +130,9 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
||||||
realm.setRealm(CHILD_IDP);
|
realm.setRealm(CHILD_IDP);
|
||||||
realm.setEnabled(true);
|
realm.setEnabled(true);
|
||||||
ClientRepresentation servlet = new ClientRepresentation();
|
ClientRepresentation servlet = new ClientRepresentation();
|
||||||
servlet.setClientId("client-linking");
|
servlet.setClientId(ClientApp.DEPLOYMENT_NAME);
|
||||||
servlet.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
servlet.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
String uri = "/client-linking";
|
String uri = "/" + ClientApp.DEPLOYMENT_NAME;
|
||||||
if (!isRelative()) {
|
if (!isRelative()) {
|
||||||
uri = appServerContextRootPage.toString() + uri;
|
uri = appServerContextRootPage.toString() + uri;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +205,7 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
||||||
|
|
||||||
public static void setupRealm(KeycloakSession session) {
|
public static void setupRealm(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName(CHILD_IDP);
|
RealmModel realm = session.realms().getRealmByName(CHILD_IDP);
|
||||||
ClientModel client = realm.getClientByClientId("client-linking");
|
ClientModel client = realm.getClientByClientId(ClientApp.DEPLOYMENT_NAME);
|
||||||
IdentityProviderModel idp = realm.getIdentityProviderByAlias(PARENT_IDP);
|
IdentityProviderModel idp = realm.getIdentityProviderByAlias(PARENT_IDP);
|
||||||
Assert.assertNotNull(idp);
|
Assert.assertNotNull(idp);
|
||||||
|
|
||||||
|
@ -213,6 +219,19 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
||||||
management.idps().exchangeToPermission(idp).addAssociatedPolicy(clientPolicy);
|
management.idps().exchangeToPermission(idp).addAssociatedPolicy(clientPolicy);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
public static void turnOffTokenStore(KeycloakSession session) {
|
||||||
|
RealmModel realm = session.realms().getRealmByName(CHILD_IDP);
|
||||||
|
IdentityProviderModel idp = realm.getIdentityProviderByAlias(PARENT_IDP);
|
||||||
|
idp.setStoreToken(false);
|
||||||
|
realm.updateIdentityProvider(idp);
|
||||||
|
|
||||||
|
}
|
||||||
|
public static void turnOnTokenStore(KeycloakSession session) {
|
||||||
|
RealmModel realm = session.realms().getRealmByName(CHILD_IDP);
|
||||||
|
IdentityProviderModel idp = realm.getIdentityProviderByAlias(PARENT_IDP);
|
||||||
|
idp.setStoreToken(true);
|
||||||
|
realm.updateIdentityProvider(idp);
|
||||||
|
}
|
||||||
@Before
|
@Before
|
||||||
public void createBroker() {
|
public void createBroker() {
|
||||||
createParentChild();
|
createParentChild();
|
||||||
|
@ -225,186 +244,101 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testErrorConditions() throws Exception {
|
public void testAccountLink() throws Exception {
|
||||||
|
testingClient.server().run(AbstractLinkAndExchangeTest::turnOnTokenStore);
|
||||||
|
|
||||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
||||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||||
Assert.assertTrue(links.isEmpty());
|
Assert.assertTrue(links.isEmpty());
|
||||||
|
|
||||||
ClientRepresentation client = adminClient.realms().realm(CHILD_IDP).clients().findByClientId("client-linking").get(0);
|
String servletUri = appPage.getInjectedUrl().toString();
|
||||||
|
UriBuilder linkBuilder = UriBuilder.fromUri(servletUri)
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
|
|
||||||
.path("link")
|
|
||||||
.queryParam("response", "true");
|
|
||||||
|
|
||||||
UriBuilder directLinking = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth")
|
|
||||||
.path("realms/child/broker/{provider}/link")
|
|
||||||
.queryParam("client_id", "client-linking")
|
|
||||||
.queryParam("redirect_uri", redirectUri.build())
|
|
||||||
.queryParam("hash", Base64Url.encode("crap".getBytes()))
|
|
||||||
.queryParam("nonce", UUID.randomUUID().toString());
|
|
||||||
|
|
||||||
String linkUrl = directLinking
|
|
||||||
.build(PARENT_IDP).toString();
|
|
||||||
|
|
||||||
// test not logged in
|
|
||||||
|
|
||||||
navigateTo(linkUrl);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
|
|
||||||
Assert.assertTrue(driver.getCurrentUrl().contains("link_error=not_logged_in"));
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
// now log in
|
|
||||||
|
|
||||||
navigateTo( appPage.getInjectedUrl() + "/hello");
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(appPage.getInjectedUrl() + "/hello"));
|
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Unknown request:"));
|
|
||||||
|
|
||||||
// now test CSRF with bad hash.
|
|
||||||
|
|
||||||
navigateTo(linkUrl);
|
|
||||||
|
|
||||||
Assert.assertTrue(driver.getPageSource().contains("We're sorry..."));
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
// now log in again with client that does not have scope
|
|
||||||
|
|
||||||
String accountId = adminClient.realms().realm(CHILD_IDP).clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0).getId();
|
|
||||||
RoleRepresentation manageAccount = adminClient.realms().realm(CHILD_IDP).clients().get(accountId).roles().get(MANAGE_ACCOUNT).toRepresentation();
|
|
||||||
RoleRepresentation manageLinks = adminClient.realms().realm(CHILD_IDP).clients().get(accountId).roles().get(MANAGE_ACCOUNT_LINKS).toRepresentation();
|
|
||||||
RoleRepresentation userRole = adminClient.realms().realm(CHILD_IDP).roles().get("user").toRepresentation();
|
|
||||||
|
|
||||||
client.setFullScopeAllowed(false);
|
|
||||||
ClientResource clientResource = adminClient.realms().realm(CHILD_IDP).clients().get(client.getId());
|
|
||||||
clientResource.update(client);
|
|
||||||
|
|
||||||
List<RoleRepresentation> roles = new LinkedList<>();
|
|
||||||
roles.add(userRole);
|
|
||||||
clientResource.getScopeMappings().realmLevel().add(roles);
|
|
||||||
|
|
||||||
navigateTo( appPage.getInjectedUrl() + "/hello");
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(appPage.getInjectedUrl() + "/hello"));
|
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Unknown request:"));
|
|
||||||
|
|
||||||
|
|
||||||
UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
|
|
||||||
.path("link");
|
.path("link");
|
||||||
String clientLinkUrl = linkBuilder.clone()
|
String linkUrl = linkBuilder.clone()
|
||||||
.queryParam("realm", CHILD_IDP)
|
.queryParam("realm", CHILD_IDP)
|
||||||
.queryParam("provider", PARENT_IDP).build().toString();
|
.queryParam("provider", PARENT_IDP).build().toString();
|
||||||
|
System.out.println("linkUrl: " + linkUrl);
|
||||||
|
navigateTo(linkUrl);
|
||||||
navigateTo(clientLinkUrl);
|
|
||||||
|
|
||||||
Assert.assertTrue(driver.getCurrentUrl().contains("error=not_allowed"));
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
// add MANAGE_ACCOUNT_LINKS scope should pass.
|
|
||||||
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
|
|
||||||
roles = new LinkedList<>();
|
|
||||||
roles.add(manageLinks);
|
|
||||||
clientResource.getScopeMappings().clientLevel(accountId).add(roles);
|
|
||||||
|
|
||||||
navigateTo(clientLinkUrl);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
||||||
|
Assert.assertTrue(driver.getPageSource().contains(PARENT_IDP));
|
||||||
loginPage.login("child", "password");
|
loginPage.login("child", "password");
|
||||||
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
|
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
|
||||||
loginPage.login(PARENT_USERNAME, "password");
|
loginPage.login(PARENT_USERNAME, "password");
|
||||||
|
System.out.println("After linking: " + driver.getCurrentUrl());
|
||||||
|
System.out.println(driver.getPageSource());
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
||||||
|
Assert.assertTrue(driver.getPageSource().contains("Exchange token received"));
|
||||||
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||||
Assert.assertFalse(links.isEmpty());
|
Assert.assertFalse(links.isEmpty());
|
||||||
|
|
||||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
clientResource.getScopeMappings().clientLevel(accountId).remove(roles);
|
|
||||||
|
// do exchange
|
||||||
|
|
||||||
|
String accessToken = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, ClientApp.DEPLOYMENT_NAME, "password").getAccessToken();
|
||||||
|
Client httpClient = ClientBuilder.newClient();
|
||||||
|
|
||||||
|
WebTarget exchangeUrl = httpClient.target(OAuthClient.AUTH_SERVER_ROOT)
|
||||||
|
.path("/realms")
|
||||||
|
.path(CHILD_IDP)
|
||||||
|
.path("protocol/openid-connect/token");
|
||||||
|
System.out.println("Exchange url: " + exchangeUrl.getUri().toString());
|
||||||
|
|
||||||
|
Response response = exchangeUrl.request()
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
|
||||||
|
.post(Entity.form(
|
||||||
|
new Form()
|
||||||
|
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
|
||||||
|
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
|
||||||
|
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
|
||||||
|
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
|
||||||
|
|
||||||
|
));
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||||
|
response.close();
|
||||||
|
String externalToken = tokenResponse.getToken();
|
||||||
|
Assert.assertNotNull(externalToken);
|
||||||
|
Assert.assertTrue(tokenResponse.getExpiresIn() > 0);
|
||||||
|
setTimeOffset((int)tokenResponse.getExpiresIn() + 1);
|
||||||
|
|
||||||
|
// test that token refresh happens
|
||||||
|
|
||||||
|
// get access token again because we may have timed out
|
||||||
|
accessToken = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, ClientApp.DEPLOYMENT_NAME, "password").getAccessToken();
|
||||||
|
response = exchangeUrl.request()
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
|
||||||
|
.post(Entity.form(
|
||||||
|
new Form()
|
||||||
|
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
|
||||||
|
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
|
||||||
|
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
|
||||||
|
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
|
||||||
|
|
||||||
|
));
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||||
|
response.close();
|
||||||
|
Assert.assertNotEquals(externalToken, tokenResponse.getToken());
|
||||||
|
|
||||||
|
|
||||||
logoutAll();
|
logoutAll();
|
||||||
|
|
||||||
navigateTo(clientLinkUrl);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
|
|
||||||
Assert.assertTrue(driver.getCurrentUrl().contains("link_error=not_allowed"));
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
// add MANAGE_ACCOUNT scope should pass
|
|
||||||
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
|
|
||||||
roles = new LinkedList<>();
|
|
||||||
roles.add(manageAccount);
|
|
||||||
clientResource.getScopeMappings().clientLevel(accountId).add(roles);
|
|
||||||
|
|
||||||
navigateTo(clientLinkUrl);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
|
|
||||||
loginPage.login(PARENT_USERNAME, "password");
|
|
||||||
|
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
|
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
|
||||||
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertFalse(links.isEmpty());
|
|
||||||
|
|
||||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
|
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||||
Assert.assertTrue(links.isEmpty());
|
Assert.assertTrue(links.isEmpty());
|
||||||
|
|
||||||
clientResource.getScopeMappings().clientLevel(accountId).remove(roles);
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
navigateTo(clientLinkUrl);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
|
|
||||||
Assert.assertTrue(driver.getCurrentUrl().contains("link_error=not_allowed"));
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
|
|
||||||
// undo fullScopeAllowed
|
|
||||||
|
|
||||||
client = adminClient.realms().realm(CHILD_IDP).clients().findByClientId("client-linking").get(0);
|
|
||||||
client.setFullScopeAllowed(true);
|
|
||||||
clientResource.update(client);
|
|
||||||
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAccountLink() throws Exception {
|
public void testAccountLinkNoTokenStore() throws Exception {
|
||||||
|
testingClient.server().run(AbstractLinkAndExchangeTest::turnOffTokenStore);
|
||||||
|
|
||||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
||||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||||
Assert.assertTrue(links.isEmpty());
|
Assert.assertTrue(links.isEmpty());
|
||||||
|
@ -425,50 +359,24 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
||||||
|
Assert.assertTrue(driver.getPageSource().contains("Exchange token received"));
|
||||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, "client-linking", "password");
|
|
||||||
Assert.assertNotNull(response.getAccessToken());
|
|
||||||
Assert.assertNull(response.getError());
|
|
||||||
Client httpClient = ClientBuilder.newClient();
|
|
||||||
String firstToken = getToken(response, httpClient);
|
|
||||||
Assert.assertNotNull(firstToken);
|
|
||||||
|
|
||||||
|
|
||||||
navigateTo(linkUrl);
|
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
|
||||||
String nextToken = getToken(response, httpClient);
|
|
||||||
Assert.assertNotNull(nextToken);
|
|
||||||
Assert.assertNotEquals(firstToken, nextToken);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||||
Assert.assertFalse(links.isEmpty());
|
Assert.assertFalse(links.isEmpty());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
logoutAll();
|
||||||
|
|
||||||
|
|
||||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
|
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||||
Assert.assertTrue(links.isEmpty());
|
Assert.assertTrue(links.isEmpty());
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getToken(OAuthClient.AccessTokenResponse response, Client httpClient) throws Exception {
|
|
||||||
String idpToken = httpClient.target(OAuthClient.AUTH_SERVER_ROOT)
|
|
||||||
.path("realms")
|
|
||||||
.path("child/broker")
|
|
||||||
.path(PARENT_IDP)
|
|
||||||
.path("token")
|
|
||||||
.request()
|
|
||||||
.header("Authorization", "Bearer " + response.getAccessToken())
|
|
||||||
.get(String.class);
|
|
||||||
AccessTokenResponse res = JsonSerialization.readValue(idpToken, AccessTokenResponse.class);
|
|
||||||
return res.getToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logoutAll() {
|
public void logoutAll() {
|
||||||
String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()).build(CHILD_IDP).toString();
|
String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()).build(CHILD_IDP).toString();
|
||||||
|
@ -477,176 +385,6 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
||||||
navigateTo(logoutUri);
|
navigateTo(logoutUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLinkOnlyProvider() throws Exception {
|
|
||||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
|
||||||
IdentityProviderRepresentation rep = realm.identityProviders().get(PARENT_IDP).toRepresentation();
|
|
||||||
rep.setLinkOnly(true);
|
|
||||||
realm.identityProviders().get(PARENT_IDP).update(rep);
|
|
||||||
try {
|
|
||||||
|
|
||||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
|
|
||||||
.path("link");
|
|
||||||
String linkUrl = linkBuilder.clone()
|
|
||||||
.queryParam("realm", CHILD_IDP)
|
|
||||||
.queryParam("provider", PARENT_IDP).build().toString();
|
|
||||||
navigateTo(linkUrl);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
|
|
||||||
// should not be on login page. This is what we are testing
|
|
||||||
Assert.assertFalse(driver.getPageSource().contains(PARENT_IDP));
|
|
||||||
|
|
||||||
// now test that we can still link.
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
|
|
||||||
loginPage.login(PARENT_USERNAME, "password");
|
|
||||||
System.out.println("After linking: " + driver.getCurrentUrl());
|
|
||||||
System.out.println(driver.getPageSource());
|
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
|
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
|
||||||
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertFalse(links.isEmpty());
|
|
||||||
|
|
||||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
System.out.println("testing link-only attack");
|
|
||||||
|
|
||||||
navigateTo(linkUrl);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
|
|
||||||
System.out.println("login page uri is: " + driver.getCurrentUrl());
|
|
||||||
|
|
||||||
// ok, now scrape the code from page
|
|
||||||
String pageSource = driver.getPageSource();
|
|
||||||
String action = ActionURIUtils.getActionURIFromPageSource(pageSource);
|
|
||||||
System.out.println("action uri: " + action);
|
|
||||||
|
|
||||||
Map<String, String> queryParams = ActionURIUtils.parseQueryParamsFromActionURI(action);
|
|
||||||
System.out.println("query params: " + queryParams);
|
|
||||||
|
|
||||||
// now try and use the code to login to remote link-only idp
|
|
||||||
|
|
||||||
String uri = "/auth/realms/child/broker/parent-idp/login";
|
|
||||||
|
|
||||||
uri = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot())
|
|
||||||
.path(uri)
|
|
||||||
.queryParam(OAuth2Constants.CODE, queryParams.get(OAuth2Constants.CODE))
|
|
||||||
.queryParam(Constants.CLIENT_ID, queryParams.get(Constants.CLIENT_ID))
|
|
||||||
.build().toString();
|
|
||||||
|
|
||||||
System.out.println("hack uri: " + uri);
|
|
||||||
|
|
||||||
navigateTo(uri);
|
|
||||||
|
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Could not send authentication request to identity provider."));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
|
|
||||||
rep.setLinkOnly(false);
|
|
||||||
realm.identityProviders().get(PARENT_IDP).update(rep);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAccountNotLinkedAutomatically() throws Exception {
|
|
||||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
|
||||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
// Login to account mgmt first
|
|
||||||
profilePage.open(CHILD_IDP);
|
|
||||||
WaitUtils.waitForPageToLoad();
|
|
||||||
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
profilePage.assertCurrent();
|
|
||||||
|
|
||||||
// Now in another tab, open login screen with "prompt=login" . Login screen will be displayed even if I have SSO cookie
|
|
||||||
UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
|
|
||||||
.path("nosuch");
|
|
||||||
String linkUrl = linkBuilder.clone()
|
|
||||||
.queryParam(OIDCLoginProtocol.PROMPT_PARAM, OIDCLoginProtocol.PROMPT_VALUE_LOGIN)
|
|
||||||
.build().toString();
|
|
||||||
|
|
||||||
navigateTo(linkUrl);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.clickSocial(PARENT_IDP);
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
|
|
||||||
loginPage.login(PARENT_USERNAME, "password");
|
|
||||||
|
|
||||||
// Test I was not automatically linked.
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
loginUpdateProfilePage.assertCurrent();
|
|
||||||
loginUpdateProfilePage.update("Joe", "Doe", "joe@parent.com");
|
|
||||||
|
|
||||||
errorPage.assertCurrent();
|
|
||||||
Assert.assertEquals("You are already authenticated as different user 'child' in this session. Please logout first.", errorPage.getError());
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
|
|
||||||
// Remove newly created user
|
|
||||||
String newUserId = ApiUtil.findUserByUsername(realm, "parent").getId();
|
|
||||||
getCleanup("child").addUserId(newUserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAccountLinkingExpired() throws Exception {
|
|
||||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
|
||||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
// Login to account mgmt first
|
|
||||||
profilePage.open(CHILD_IDP);
|
|
||||||
WaitUtils.waitForPageToLoad();
|
|
||||||
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
|
||||||
loginPage.login("child", "password");
|
|
||||||
profilePage.assertCurrent();
|
|
||||||
|
|
||||||
// Now in another tab, request account linking
|
|
||||||
UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
|
|
||||||
.path("link");
|
|
||||||
String linkUrl = linkBuilder.clone()
|
|
||||||
.queryParam("realm", CHILD_IDP)
|
|
||||||
.queryParam("provider", PARENT_IDP).build().toString();
|
|
||||||
navigateTo(linkUrl);
|
|
||||||
|
|
||||||
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
|
|
||||||
|
|
||||||
// Logout "child" userSession in the meantime (for example through admin request)
|
|
||||||
realm.logoutAll();
|
|
||||||
|
|
||||||
// Finish login on parent.
|
|
||||||
loginPage.login(PARENT_USERNAME, "password");
|
|
||||||
|
|
||||||
// Test I was not automatically linked
|
|
||||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
|
||||||
Assert.assertTrue(links.isEmpty());
|
|
||||||
|
|
||||||
errorPage.assertCurrent();
|
|
||||||
Assert.assertEquals("Requested broker account linking, but current session is no longer valid.", errorPage.getError());
|
|
||||||
|
|
||||||
logoutAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void navigateTo(String uri) {
|
private void navigateTo(String uri) {
|
||||||
driver.navigate().to(uri);
|
driver.navigate().to(uri);
|
||||||
WaitUtils.waitForPageToLoad();
|
WaitUtils.waitForPageToLoad();
|
||||||
|
|
|
@ -39,4 +39,10 @@ public class UndertowLinkAndExchangeTest extends AbstractLinkAndExchangeTest {
|
||||||
public void testAccountLink() throws Exception {
|
public void testAccountLink() throws Exception {
|
||||||
super.testAccountLink();
|
super.testAccountLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testAccountLinkNoTokenStore() throws Exception {
|
||||||
|
super.testAccountLinkNoTokenStore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!--
|
||||||
|
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ and other contributors as indicated by the @author tags.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Context path="/customer-portal">
|
||||||
|
<Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
|
||||||
|
</Context>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ and other contributors as indicated by the @author tags.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
||||||
|
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||||
|
<Get name="securityHandler">
|
||||||
|
<Set name="authenticator">
|
||||||
|
<New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
|
||||||
|
<!--
|
||||||
|
<Set name="adapterConfig">
|
||||||
|
<New class="org.keycloak.representations.adapters.config.AdapterConfig">
|
||||||
|
<Set name="realm">tomcat</Set>
|
||||||
|
<Set name="resource">customer-portal</Set>
|
||||||
|
<Set name="authServerUrl">http://localhost:8180/auth</Set>
|
||||||
|
<Set name="sslRequired">external</Set>
|
||||||
|
<Set name="credentials">
|
||||||
|
<Map>
|
||||||
|
<Entry>
|
||||||
|
<Item>secret</Item>
|
||||||
|
<Item>password</Item>
|
||||||
|
</Entry>
|
||||||
|
</Map>
|
||||||
|
</Set>
|
||||||
|
<Set name="realmKey">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</Set>
|
||||||
|
</New>
|
||||||
|
</Set>
|
||||||
|
-->
|
||||||
|
</New>
|
||||||
|
</Set>
|
||||||
|
</Get>
|
||||||
|
</Configure>
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"realm" : "child",
|
||||||
|
"resource" : "exchange-linking",
|
||||||
|
"auth-server-url" : "http://localhost:8180/auth",
|
||||||
|
"ssl-required" : "external",
|
||||||
|
"min-time-between-jwks-requests" : 0,
|
||||||
|
"credentials" : {
|
||||||
|
"secret": "password"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ and other contributors as indicated by the @author tags.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||||
|
version="3.0">
|
||||||
|
|
||||||
|
<module-name>exchange-linking</module-name>
|
||||||
|
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>Servlet</servlet-name>
|
||||||
|
<servlet-class>org.keycloak.testsuite.adapter.servlet.LinkAndExchangeServlet</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>Servlet</servlet-name>
|
||||||
|
<url-pattern>/*</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<security-constraint>
|
||||||
|
<web-resource-collection>
|
||||||
|
<web-resource-name>Users</web-resource-name>
|
||||||
|
<url-pattern>/*</url-pattern>
|
||||||
|
</web-resource-collection>
|
||||||
|
<auth-constraint>
|
||||||
|
<role-name>user</role-name>
|
||||||
|
</auth-constraint>
|
||||||
|
</security-constraint>
|
||||||
|
|
||||||
|
<login-config>
|
||||||
|
<auth-method>KEYCLOAK</auth-method>
|
||||||
|
<realm-name>child</realm-name>
|
||||||
|
</login-config>
|
||||||
|
|
||||||
|
<security-role>
|
||||||
|
<role-name>user</role-name>
|
||||||
|
</security-role>
|
||||||
|
</web-app>
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"id": "child",
|
||||||
|
"realm": "child",
|
||||||
|
"enabled": true,
|
||||||
|
"accessTokenLifespan": 600,
|
||||||
|
"accessCodeLifespan": 10,
|
||||||
|
"accessCodeLifespanUserAction": 6000,
|
||||||
|
"sslRequired": "external",
|
||||||
|
"registrationAllowed": false,
|
||||||
|
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||||
|
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
|
"requiredCredentials": [ "password" ],
|
||||||
|
"users" : [
|
||||||
|
{
|
||||||
|
"username" : "bburke@redhat.com",
|
||||||
|
"enabled": true,
|
||||||
|
"email" : "bburke@redhat.com",
|
||||||
|
"firstName": "Bill",
|
||||||
|
"lastName": "Burke",
|
||||||
|
"credentials" : [
|
||||||
|
{ "type" : "password",
|
||||||
|
"value" : "password" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"clientId": "exchange-linking",
|
||||||
|
"enabled": true,
|
||||||
|
"adminUrl": "/exchange-linking",
|
||||||
|
"baseUrl": "/exchange-linking",
|
||||||
|
"redirectUris": [
|
||||||
|
"/exchange-linking/*"
|
||||||
|
],
|
||||||
|
"secret": "password"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1344,7 +1344,8 @@ manage-permissions-group.tooltip=Fine grain permssions for admins that want to m
|
||||||
manage-authz-group-scope-description=Policies that decide if an admin can manage this group
|
manage-authz-group-scope-description=Policies that decide if an admin can manage this group
|
||||||
view-authz-group-scope-description=Policies that decide if an admin can view this group
|
view-authz-group-scope-description=Policies that decide if an admin can view this group
|
||||||
view-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group
|
view-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group
|
||||||
exchange-to-authz-client-scope-description=Policies that decide which clients are allowed exchange tokens for a token that is targeted to this client.
|
token-exchange-authz-client-scope-description=Policies that decide which clients are allowed exchange tokens for a token that is targeted to this client.
|
||||||
|
token-exchange-authz-idp-scope-description=Policies that decide which clients are allowed exchange tokens for an external token minted by this identity provider.
|
||||||
manage-authz-client-scope-description=Policies that decide if an admin can manage this client
|
manage-authz-client-scope-description=Policies that decide if an admin can manage this client
|
||||||
configure-authz-client-scope-description=Reduced management permissions for admin. Cannot set scope, template, or protocol mappers.
|
configure-authz-client-scope-description=Reduced management permissions for admin. Cannot set scope, template, or protocol mappers.
|
||||||
view-authz-client-scope-description=Policies that decide if an admin can view this client
|
view-authz-client-scope-description=Policies that decide if an admin can view this client
|
||||||
|
|
|
@ -470,6 +470,18 @@ module.config(['$routeProvider', function ($routeProvider) {
|
||||||
},
|
},
|
||||||
controller : 'GroupPermissionsCtrl'
|
controller : 'GroupPermissionsCtrl'
|
||||||
})
|
})
|
||||||
|
.when('/realms/:realm/identity-provider-settings/provider/:provider_id/:alias/permissions', {
|
||||||
|
templateUrl : function(params){ return resourceUrl + '/partials/authz/mgmt/broker-permissions.html'; },
|
||||||
|
resolve : {
|
||||||
|
realm : function(RealmLoader) {
|
||||||
|
return RealmLoader();
|
||||||
|
},
|
||||||
|
identityProvider : function(IdentityProviderLoader) {
|
||||||
|
return IdentityProviderLoader();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controller : 'IdentityProviderPermissionCtrl'
|
||||||
|
})
|
||||||
;
|
;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
|
@ -2544,7 +2544,6 @@ module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $l
|
||||||
$scope.permissions = data;
|
$scope.permissions = data;
|
||||||
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||||
if (newVal != oldVal) {
|
if (newVal != oldVal) {
|
||||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
|
||||||
var param = {enabled: $scope.permissions.enabled};
|
var param = {enabled: $scope.permissions.enabled};
|
||||||
$scope.permissions= RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param);
|
$scope.permissions= RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param);
|
||||||
}
|
}
|
||||||
|
@ -2563,7 +2562,6 @@ module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $
|
||||||
$scope.permissions = data;
|
$scope.permissions = data;
|
||||||
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||||
if (newVal != oldVal) {
|
if (newVal != oldVal) {
|
||||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
|
||||||
var param = {enabled: $scope.permissions.enabled};
|
var param = {enabled: $scope.permissions.enabled};
|
||||||
$scope.permissions = RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param);
|
$scope.permissions = RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param);
|
||||||
}
|
}
|
||||||
|
@ -2582,7 +2580,6 @@ module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $locat
|
||||||
$scope.permissions = data;
|
$scope.permissions = data;
|
||||||
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||||
if (newVal != oldVal) {
|
if (newVal != oldVal) {
|
||||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
|
||||||
var param = {enabled: $scope.permissions.enabled};
|
var param = {enabled: $scope.permissions.enabled};
|
||||||
$scope.permissions = UsersManagementPermissions.update({realm: realm.realm}, param);
|
$scope.permissions = UsersManagementPermissions.update({realm: realm.realm}, param);
|
||||||
|
|
||||||
|
@ -2605,7 +2602,6 @@ module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $loca
|
||||||
$scope.permissions = data;
|
$scope.permissions = data;
|
||||||
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||||
if (newVal != oldVal) {
|
if (newVal != oldVal) {
|
||||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
|
||||||
var param = {enabled: $scope.permissions.enabled};
|
var param = {enabled: $scope.permissions.enabled};
|
||||||
$scope.permissions = ClientManagementPermissions.update({realm: realm.realm, client: client.id}, param);
|
$scope.permissions = ClientManagementPermissions.update({realm: realm.realm, client: client.id}, param);
|
||||||
}
|
}
|
||||||
|
@ -2616,6 +2612,23 @@ module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $loca
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.controller('IdentityProviderPermissionCtrl', function($scope, $http, $route, $location, realm, identityProvider, Client, IdentityProviderManagementPermissions, Notifications) {
|
||||||
|
$scope.identityProvider = identityProvider;
|
||||||
|
$scope.realm = realm;
|
||||||
|
IdentityProviderManagementPermissions.get({realm: realm.realm, alias: identityProvider.alias}, function(data) {
|
||||||
|
$scope.permissions = data;
|
||||||
|
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||||
|
if (newVal != oldVal) {
|
||||||
|
var param = {enabled: $scope.permissions.enabled};
|
||||||
|
$scope.permissions = IdentityProviderManagementPermissions.update({realm: realm.realm, alias: identityProvider.alias}, param);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) {
|
||||||
|
$scope.realmManagementClientId = data[0].id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
module.controller('GroupPermissionsCtrl', function($scope, $http, $route, $location, realm, group, GroupManagementPermissions, Client, Notifications) {
|
module.controller('GroupPermissionsCtrl', function($scope, $http, $route, $location, realm, group, GroupManagementPermissions, Client, Notifications) {
|
||||||
$scope.group = group;
|
$scope.group = group;
|
||||||
$scope.realm = realm;
|
$scope.realm = realm;
|
||||||
|
@ -2626,7 +2639,6 @@ module.controller('GroupPermissionsCtrl', function($scope, $http, $route, $locat
|
||||||
$scope.permissions = data;
|
$scope.permissions = data;
|
||||||
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||||
if (newVal != oldVal) {
|
if (newVal != oldVal) {
|
||||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
|
||||||
var param = {enabled: $scope.permissions.enabled};
|
var param = {enabled: $scope.permissions.enabled};
|
||||||
$scope.permissions = GroupManagementPermissions.update({realm: realm.realm, group: group.id}, param);
|
$scope.permissions = GroupManagementPermissions.update({realm: realm.realm, group: group.id}, param);
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,6 +178,17 @@ module.factory('ClientManagementPermissions', function($resource) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.factory('IdentityProviderManagementPermissions', function($resource) {
|
||||||
|
return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/management/permissions', {
|
||||||
|
realm : '@realm',
|
||||||
|
alias : '@alias'
|
||||||
|
}, {
|
||||||
|
update: {
|
||||||
|
method: 'PUT'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
module.factory('GroupManagementPermissions', function($resource) {
|
module.factory('GroupManagementPermissions', function($resource) {
|
||||||
return $resource(authUrl + '/admin/realms/:realm/groups/:group/management/permissions', {
|
return $resource(authUrl + '/admin/realms/:realm/groups/:group/management/permissions', {
|
||||||
realm : '@realm',
|
realm : '@realm',
|
||||||
|
|
|
@ -1001,7 +1001,7 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
|
||||||
} else {
|
} else {
|
||||||
IdentityProvider.update({
|
IdentityProvider.update({
|
||||||
realm: $scope.realm.realm,
|
realm: $scope.realm.realm,
|
||||||
id: $scope.identityProvider.internalId
|
alias: $scope.identityProvider.alias
|
||||||
}, $scope.identityProvider, function () {
|
}, $scope.identityProvider, function () {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("The " + $scope.identityProvider.alias + " provider has been updated.");
|
Notifications.success("The " + $scope.identityProvider.alias + " provider has been updated.");
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
|
||||||
|
<li data-ng-show="!newIdentityProvider && identityProvider.displayName">{{identityProvider.displayName}}</li>
|
||||||
|
<li data-ng-show="!newIdentityProvider && !identityProvider.displayName">{{identityProvider.alias}}</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<kc-tabs-identity-provider></kc-tabs-identity-provider>
|
||||||
|
|
||||||
|
<form class=form-horizontal" name="enableForm" novalidate kc-read-only="!access.manageIdentityProviders || !access.manageAuthorization">
|
||||||
|
<fieldset class="border-top">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="permissionsEnabled">{{:: 'permissions-enabled-role' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="permissions.enabled" name="permissionsEnabled" id="permissionsEnabled" ng-disabled="!access.manageAuthorization" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'permissions-enabled-role.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<table class="datatable table table-striped table-bordered dataTable no-footer" data-ng-show="permissions.enabled">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{:: 'scope-name' | translate}}</th>
|
||||||
|
<th>{{:: 'description' | translate}}</th>
|
||||||
|
<th colspan="2">{{:: 'actions' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="(scopeName, scopeId) in permissions.scopePermissions">
|
||||||
|
<td><a href="#/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{scopeName}}</a></td>
|
||||||
|
<td translate="{{scopeName}}-authz-idp-scope-description"></td>
|
||||||
|
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{:: 'edit' | translate}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kc-menu></kc-menu>
|
|
@ -12,5 +12,6 @@
|
||||||
<li ng-class="{active: !path[6] && path.length > 5}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{:: 'settings' | translate}}</a></li>
|
<li ng-class="{active: !path[6] && path.length > 5}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{:: 'settings' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[4] == 'mappers'}"><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">{{:: 'mappers' | translate}}</a></li>
|
<li ng-class="{active: path[4] == 'mappers'}"><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">{{:: 'mappers' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[6] == 'export'}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider && identityProvider.providerId == 'saml'">{{:: 'export' | translate}}</a></li>
|
<li ng-class="{active: path[6] == 'export'}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider && identityProvider.providerId == 'saml'">{{:: 'export' | translate}}</a></li>
|
||||||
|
<li ng-class="{active: path[4] == 'permissions'}" data-ng-show="!newIdentityProvider && access.manageAuthorization"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/permissions">{{:: 'authz-permissions' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue