KEYCLOAK-5490 (#4477)
This commit is contained in:
parent
6ec5264f20
commit
3e6adbc904
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) {
|
||||
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) {
|
||||
|
|
|
@ -29,7 +29,7 @@ import javax.ws.rs.core.UriInfo;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface TokenExchangeTo {
|
||||
public interface ExchangeTokenToIdentityProviderToken {
|
||||
/**
|
||||
*
|
||||
* @param authorizedClient client requesting exchange
|
||||
|
@ -39,5 +39,5 @@ public interface TokenExchangeTo {
|
|||
* @param params form parameters received for requested exchange
|
||||
* @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.BrokeredIdentityContext;
|
||||
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.common.ClientConnection;
|
||||
import org.keycloak.events.Details;
|
||||
|
@ -62,7 +62,7 @@ import java.util.regex.Pattern;
|
|||
/**
|
||||
* @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);
|
||||
|
||||
public static final String OAUTH2_GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
|
||||
|
@ -148,7 +148,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
}
|
||||
|
||||
@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);
|
||||
if (requestedType != null && !requestedType.equals(OAuth2Constants.ACCESS_TOKEN_TYPE)) {
|
||||
return exchangeUnsupportedRequiredType();
|
||||
|
@ -156,7 +156,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
if (!getConfig().isStoreToken()) {
|
||||
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
|
||||
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
|
||||
return exchangeNotSupported();
|
||||
return exchangeNotLinkedNoStore(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||
}
|
||||
return exchangeSessionToken(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||
} else {
|
||||
|
@ -317,7 +317,9 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
BrokeredIdentityContext federatedIdentity = getFederatedIdentity(response);
|
||||
|
||||
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());
|
||||
|
|
|
@ -231,9 +231,10 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
return exchangeNotLinked(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||
}
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(model.getToken(), AccessTokenResponse.class);
|
||||
Long exp = (Long)tokenResponse.getOtherClaims().get(ACCESS_TOKEN_EXPIRATION);
|
||||
if (exp != null && (long)exp < Time.currentTime()) {
|
||||
String modelTokenString = model.getToken();
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(modelTokenString, AccessTokenResponse.class);
|
||||
Integer exp = (Integer)tokenResponse.getOtherClaims().get(ACCESS_TOKEN_EXPIRATION);
|
||||
if (exp != null && exp < Time.currentTime()) {
|
||||
if (tokenResponse.getRefreshToken() == null) {
|
||||
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_SECRET, getConfig().getClientSecret()).asString();
|
||||
if (response.contains("error")) {
|
||||
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
|
||||
model.setToken(null);
|
||||
session.users().updateFederatedIdentity(authorizedClient.getRealm(), tokenSubject, model);
|
||||
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||
}
|
||||
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
||||
if (newResponse.getExpiresIn() > 0) {
|
||||
long accessTokenExpiration = Time.currentTime() + newResponse.getExpiresIn();
|
||||
int accessTokenExpiration = Time.currentTime() + (int)newResponse.getExpiresIn();
|
||||
newResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
|
||||
response = JsonSerialization.writeValueAsString(newResponse);
|
||||
}
|
||||
String oldToken = tokenUserSession.getNote(FEDERATED_ACCESS_TOKEN);
|
||||
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_REFRESH_TOKEN, newResponse.getRefreshToken());
|
||||
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_SECRET, getConfig().getClientSecret()).asString();
|
||||
if (response.contains("error")) {
|
||||
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
|
||||
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||
}
|
||||
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
||||
|
@ -341,13 +344,11 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
BrokeredIdentityContext identity = extractIdentity(tokenResponse, accessToken, idToken);
|
||||
|
||||
if (getConfig().isStoreToken()) {
|
||||
String response1 = response;
|
||||
if (tokenResponse.getExpiresIn() > 0) {
|
||||
long accessTokenExpiration = Time.currentTime() + tokenResponse.getExpiresIn();
|
||||
tokenResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
|
||||
response1 = JsonSerialization.writeValueAsString(tokenResponse);
|
||||
response = JsonSerialization.writeValueAsString(tokenResponse);
|
||||
}
|
||||
response = response1;
|
||||
identity.setToken(response);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
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.constants.ServiceAccountConstants;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
|
@ -39,7 +39,6 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
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.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -587,7 +585,7 @@ public class TokenEndpoint {
|
|||
String requestedIssuer = formParams.getFirst(OAuth2Constants.REQUESTED_ISSUER);
|
||||
|
||||
if (requestedIssuer == null) {
|
||||
return exchangeClientToClient(authResult);
|
||||
return exchangeClientToClient(authResult.getUser(), authResult.getSession());
|
||||
} else {
|
||||
return exchangeToIdentityProvider(authResult, requestedIssuer);
|
||||
}
|
||||
|
@ -601,7 +599,7 @@ public class TokenEndpoint {
|
|||
}
|
||||
|
||||
IdentityProvider provider = IdentityBrokerService.getIdentityProvider(session, realm, requestedIssuer);
|
||||
if (!(provider instanceof TokenExchangeTo)) {
|
||||
if (!(provider instanceof ExchangeTokenToIdentityProviderToken)) {
|
||||
event.error(Errors.UNKNOWN_IDENTITY_PROVIDER);
|
||||
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);
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
public Response exchangeClientToClient(AuthenticationManager.AuthResult subject) {
|
||||
protected Response exchangeClientToClient(UserModel targetUser, UserSessionModel targetUserSession) {
|
||||
String requestedTokenType = formParams.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
|
||||
if (requestedTokenType == null) {
|
||||
requestedTokenType = OAuth2Constants.REFRESH_TOKEN_TYPE;
|
||||
|
@ -653,25 +651,19 @@ public class TokenEndpoint {
|
|||
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
|
||||
|
||||
AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, targetClient, false);
|
||||
authSession.setAuthenticatedUser(subject.getUser());
|
||||
authSession.setAuthenticatedUser(targetUser);
|
||||
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
||||
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
|
||||
|
||||
UserSessionModel userSession = subject.getSession();
|
||||
event.session(userSession);
|
||||
event.session(targetUserSession);
|
||||
|
||||
AuthenticationManager.setRolesAndMappersInSession(authSession);
|
||||
AuthenticatedClientSessionModel clientSession = TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
||||
AuthenticatedClientSessionModel clientSession = TokenManager.attachAuthenticationSession(this.session, targetUserSession, authSession);
|
||||
|
||||
// Notes about client details
|
||||
userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
|
||||
userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
|
||||
userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteAddr());
|
||||
updateUserSessionFromClientAuth(targetUserSession);
|
||||
|
||||
updateUserSessionFromClientAuth(userSession);
|
||||
|
||||
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, session, userSession, clientSession)
|
||||
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, this.session, targetUserSession, clientSession)
|
||||
.generateAccessToken();
|
||||
responseBuilder.getAccessToken().issuedFor(client.getClientId());
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.keycloak.models.ClientModel;
|
|||
public interface AdminPermissionManagement {
|
||||
public static final String MANAGE_SCOPE = "manage";
|
||||
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();
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ import java.util.LinkedHashMap;
|
|||
import java.util.Map;
|
||||
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.
|
||||
|
@ -87,7 +87,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -107,7 +107,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
|||
Scope mapRoleClientScope = root.initializeScope(MAP_ROLES_CLIENT_SCOPE, server);
|
||||
Scope mapRoleCompositeScope = root.initializeScope(MAP_ROLES_COMPOSITE_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);
|
||||
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
|
||||
|
@ -207,7 +207,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -293,7 +293,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
|||
scopes.put(MAP_ROLES_SCOPE, mapRolesPermission(client).getId());
|
||||
scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(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;
|
||||
}
|
||||
|
||||
|
@ -328,7 +328,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
|
|||
|
||||
Scope scope = exchangeToScope(server);
|
||||
if (scope == null) {
|
||||
logger.debug(EXCHANGE_TO_SCOPE + " not initialized");
|
||||
logger.debug(TOKEN_EXCHANGE + " not initialized");
|
||||
return false;
|
||||
}
|
||||
ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient);
|
||||
|
|
|
@ -37,10 +37,10 @@ import java.util.LinkedHashMap;
|
|||
import java.util.Map;
|
||||
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>
|
||||
|
@ -65,12 +65,12 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
|
|||
}
|
||||
|
||||
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) {
|
||||
ResourceServer server = root.initializeRealmResourceServer();
|
||||
Scope exchangeToScope = root.initializeScope(EXCHANGE_TO_SCOPE, server);
|
||||
Scope exchangeToScope = root.initializeScope(TOKEN_EXCHANGE, server);
|
||||
|
||||
String resourceName = getResourceName(idp);
|
||||
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
|
||||
|
@ -124,7 +124,7 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
|
|||
|
||||
|
||||
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
|
||||
|
@ -141,7 +141,7 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
|
|||
public Map<String, String> getPermissions(IdentityProviderModel idp) {
|
||||
initialize(idp);
|
||||
Map<String, String> scopes = new LinkedHashMap<>();
|
||||
scopes.put(EXCHANGE_TO_SCOPE, exchangeToPermission(idp).getId());
|
||||
scopes.put(TOKEN_EXCHANGE, exchangeToPermission(idp).getId());
|
||||
return scopes;
|
||||
}
|
||||
|
||||
|
@ -176,7 +176,7 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
|
|||
|
||||
Scope scope = exchangeToScope(server);
|
||||
if (scope == null) {
|
||||
logger.debug(EXCHANGE_TO_SCOPE + " not initialized");
|
||||
logger.debug(TOKEN_EXCHANGE + " not initialized");
|
||||
return false;
|
||||
}
|
||||
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.BrokeredIdentityContext;
|
||||
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.common.ClientConnection;
|
||||
import org.keycloak.events.Details;
|
||||
|
@ -61,7 +61,7 @@ import java.net.URI;
|
|||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2IdentityProviderConfig> implements
|
||||
SocialIdentityProvider<OAuth2IdentityProviderConfig>, TokenExchangeTo {
|
||||
SocialIdentityProvider<OAuth2IdentityProviderConfig>, ExchangeTokenToIdentityProviderToken {
|
||||
|
||||
String TWITTER_TOKEN_TYPE="twitter";
|
||||
|
||||
|
@ -103,7 +103,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
}
|
||||
|
||||
@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);
|
||||
if (requestedType != null && !requestedType.equals(TWITTER_TOKEN_TYPE)) {
|
||||
return exchangeUnsupportedRequiredType();
|
||||
|
@ -111,7 +111,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
if (!getConfig().isStoreToken()) {
|
||||
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
|
||||
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
|
||||
return exchangeNotSupported();
|
||||
return exchangeNotLinkedNoStore(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||
}
|
||||
return exchangeSessionToken(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
|
||||
} else {
|
||||
|
|
|
@ -47,7 +47,7 @@ import java.util.Map;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@WebServlet("/client-linking")
|
||||
@WebServlet("/exchange-linking")
|
||||
public class LinkAndExchangeServlet extends HttpServlet {
|
||||
|
||||
private String getPostDataString(HashMap<String, String> params) throws UnsupportedEncodingException{
|
||||
|
@ -100,9 +100,18 @@ public class LinkAndExchangeServlet extends HttpServlet {
|
|||
writer.flush();
|
||||
writer.close();
|
||||
os.close();
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(conn.getInputStream(), AccessTokenResponse.class);
|
||||
conn.getInputStream().close();
|
||||
return tokenResponse;
|
||||
if (conn.getResponseCode() == 200) {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(conn.getInputStream(), AccessTokenResponse.class);
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +152,9 @@ public class LinkAndExchangeServlet extends HttpServlet {
|
|||
|
||||
String redirectUri = KeycloakUriBuilder.fromUri(request.getRequestURL().toString())
|
||||
.replaceQuery(null)
|
||||
.queryParam("response", "true").build().toString();
|
||||
.queryParam("response", "true")
|
||||
.queryParam("realm", realm)
|
||||
.queryParam("provider", provider).build().toString();
|
||||
String accountLinkUrl = KeycloakUriBuilder.fromUri(linkUrl)
|
||||
.queryParam("redirect_uri", redirectUri).build().toString();
|
||||
resp.setStatus(302);
|
||||
|
@ -159,6 +170,24 @@ public class LinkAndExchangeServlet extends HttpServlet {
|
|||
} else {
|
||||
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.flush();
|
||||
} else {
|
||||
|
|
|
@ -59,10 +59,16 @@ import org.keycloak.testsuite.pages.LoginPage;
|
|||
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
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 java.net.URL;
|
||||
import java.util.LinkedList;
|
||||
|
@ -98,7 +104,7 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
|||
|
||||
public static class ClientApp extends AbstractPageWithInjectedUrl {
|
||||
|
||||
public static final String DEPLOYMENT_NAME = "client-linking";
|
||||
public static final String DEPLOYMENT_NAME = "exchange-linking";
|
||||
|
||||
@ArquillianResource
|
||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
||||
|
@ -124,9 +130,9 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
|||
realm.setRealm(CHILD_IDP);
|
||||
realm.setEnabled(true);
|
||||
ClientRepresentation servlet = new ClientRepresentation();
|
||||
servlet.setClientId("client-linking");
|
||||
servlet.setClientId(ClientApp.DEPLOYMENT_NAME);
|
||||
servlet.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
String uri = "/client-linking";
|
||||
String uri = "/" + ClientApp.DEPLOYMENT_NAME;
|
||||
if (!isRelative()) {
|
||||
uri = appServerContextRootPage.toString() + uri;
|
||||
}
|
||||
|
@ -199,7 +205,7 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
|||
|
||||
public static void setupRealm(KeycloakSession session) {
|
||||
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);
|
||||
Assert.assertNotNull(idp);
|
||||
|
||||
|
@ -213,6 +219,19 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
|||
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
|
||||
public void createBroker() {
|
||||
createParentChild();
|
||||
|
@ -225,186 +244,101 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
|||
|
||||
|
||||
@Test
|
||||
public void testErrorConditions() throws Exception {
|
||||
public void testAccountLink() throws Exception {
|
||||
testingClient.server().run(AbstractLinkAndExchangeTest::turnOnTokenStore);
|
||||
|
||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
Assert.assertTrue(links.isEmpty());
|
||||
|
||||
ClientRepresentation client = adminClient.realms().realm(CHILD_IDP).clients().findByClientId("client-linking").get(0);
|
||||
|
||||
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())
|
||||
String servletUri = appPage.getInjectedUrl().toString();
|
||||
UriBuilder linkBuilder = UriBuilder.fromUri(servletUri)
|
||||
.path("link");
|
||||
String clientLinkUrl = linkBuilder.clone()
|
||||
String linkUrl = linkBuilder.clone()
|
||||
.queryParam("realm", CHILD_IDP)
|
||||
.queryParam("provider", PARENT_IDP).build().toString();
|
||||
|
||||
|
||||
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);
|
||||
System.out.println("linkUrl: " + linkUrl);
|
||||
navigateTo(linkUrl);
|
||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
||||
Assert.assertTrue(driver.getPageSource().contains(PARENT_IDP));
|
||||
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"));
|
||||
Assert.assertTrue(driver.getPageSource().contains("Exchange token received"));
|
||||
|
||||
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());
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
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
|
||||
public void testAccountLink() throws Exception {
|
||||
public void testAccountLinkNoTokenStore() throws Exception {
|
||||
testingClient.server().run(AbstractLinkAndExchangeTest::turnOffTokenStore);
|
||||
|
||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
Assert.assertTrue(links.isEmpty());
|
||||
|
@ -425,50 +359,24 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
|||
System.out.println(driver.getPageSource());
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
|
||||
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Assert.assertTrue(driver.getPageSource().contains("Exchange token received"));
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
Assert.assertFalse(links.isEmpty());
|
||||
|
||||
|
||||
|
||||
logoutAll();
|
||||
|
||||
|
||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
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() {
|
||||
String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()).build(CHILD_IDP).toString();
|
||||
|
@ -477,176 +385,6 @@ public abstract class AbstractLinkAndExchangeTest extends AbstractServletsAdapte
|
|||
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) {
|
||||
driver.navigate().to(uri);
|
||||
WaitUtils.waitForPageToLoad();
|
||||
|
|
|
@ -39,4 +39,10 @@ public class UndertowLinkAndExchangeTest extends AbstractLinkAndExchangeTest {
|
|||
public void testAccountLink() throws Exception {
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -470,6 +470,18 @@ module.config(['$routeProvider', function ($routeProvider) {
|
|||
},
|
||||
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.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||
if (newVal != oldVal) {
|
||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
||||
var param = {enabled: $scope.permissions.enabled};
|
||||
$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.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||
if (newVal != oldVal) {
|
||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
||||
var param = {enabled: $scope.permissions.enabled};
|
||||
$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.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||
if (newVal != oldVal) {
|
||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
||||
var param = {enabled: $scope.permissions.enabled};
|
||||
$scope.permissions = UsersManagementPermissions.update({realm: realm.realm}, param);
|
||||
|
||||
|
@ -2605,7 +2602,6 @@ module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $loca
|
|||
$scope.permissions = data;
|
||||
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||
if (newVal != oldVal) {
|
||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
||||
var param = {enabled: $scope.permissions.enabled};
|
||||
$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) {
|
||||
$scope.group = group;
|
||||
$scope.realm = realm;
|
||||
|
@ -2626,7 +2639,6 @@ module.controller('GroupPermissionsCtrl', function($scope, $http, $route, $locat
|
|||
$scope.permissions = data;
|
||||
$scope.$watch('permissions.enabled', function(newVal, oldVal) {
|
||||
if (newVal != oldVal) {
|
||||
console.log('Changing permissions enabled to: ' + $scope.permissions.enabled);
|
||||
var param = {enabled: $scope.permissions.enabled};
|
||||
$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) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:group/management/permissions', {
|
||||
realm : '@realm',
|
||||
|
|
|
@ -1001,7 +1001,7 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
|
|||
} else {
|
||||
IdentityProvider.update({
|
||||
realm: $scope.realm.realm,
|
||||
id: $scope.identityProvider.internalId
|
||||
alias: $scope.identityProvider.alias
|
||||
}, $scope.identityProvider, function () {
|
||||
$route.reload();
|
||||
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[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[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>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue