keycloak-aplcache
Changes
server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java 6(+5 -1)
server-spi-private/src/main/java/org/keycloak/broker/provider/ExchangeTokenToIdentityProviderToken.java 4(+2 -2)
services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java 2(+1 -1)
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java 12(+6 -6)
services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java 14(+7 -7)
testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/LinkAndExchangeServlet.java 39(+34 -5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractLinkAndExchangeTest.java 436(+87 -349)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowLinkAndExchangeTest.java 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/childrealm.json 38(+38 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/META-INF/context.xml 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/jetty-web.xml 46(+46 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/keycloak.json 10(+10 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/web.xml 54(+54 -0)
Details
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
index 231dbc1..cd9a421 100755
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
@@ -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) {
diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index 7c754c2..468dc4b 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -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());
diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index 316d493..ca6a76f 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -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);
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index a3d6e0c..2a2f80e 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -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);
-
- // 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());
+ AuthenticatedClientSessionModel clientSession = TokenManager.attachAuthenticationSession(this.session, targetUserSession, authSession);
- updateUserSessionFromClientAuth(userSession);
+ updateUserSessionFromClientAuth(targetUserSession);
- 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());
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
index 4dfce43..b35e5b4 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
@@ -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();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
index 149b313..1c5978e 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
@@ -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);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java
index 9be37d6..a0e2e44 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java
@@ -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);
diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index ad9dc40..a3312d8 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -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 {
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/LinkAndExchangeServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/LinkAndExchangeServlet.java
index 65a945e..44ebb6d 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/LinkAndExchangeServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/LinkAndExchangeServlet.java
@@ -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 {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractLinkAndExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractLinkAndExchangeTest.java
index 312517c..a382f27 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractLinkAndExchangeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractLinkAndExchangeTest.java
@@ -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);
-
- 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);
+ // do exchange
- logoutAll();
+ String accessToken = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, ClientApp.DEPLOYMENT_NAME, "password").getAccessToken();
+ Client httpClient = ClientBuilder.newClient();
- navigateTo(clientLinkUrl);
- Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
- loginPage.login("child", "password");
+ 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());
- 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);
-
+ realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
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"));
+ 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();
+ Assert.assertFalse(links.isEmpty());
+ logoutAll();
- 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();
}
- 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();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowLinkAndExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowLinkAndExchangeTest.java
index 5230156..a2059b9 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowLinkAndExchangeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowLinkAndExchangeTest.java
@@ -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();
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/childrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/childrealm.json
new file mode 100644
index 0000000..5d624f4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/childrealm.json
@@ -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"
+ }
+ ]
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/META-INF/context.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/META-INF/context.xml
new file mode 100644
index 0000000..b4ddcce
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/META-INF/context.xml
@@ -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>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..8c59313
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/jetty-web.xml
@@ -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>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/keycloak.json
new file mode 100644
index 0000000..7b41211
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/keycloak.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/web.xml
new file mode 100644
index 0000000..4e87ad2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/exchange-linking/WEB-INF/web.xml
@@ -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>
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 3ae3500..8848371 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -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
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
index a59ebf3..fbd5379 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
@@ -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'
+ })
;
}]);
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
index 76dc5aa..9233a04 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
@@ -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);
}
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js
index f56ca8f..12315ed 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js
@@ -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',
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 2644379..ad8a407 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -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.");
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/broker-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/broker-permissions.html
new file mode 100644
index 0000000..2e389ff
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/broker-permissions.html
@@ -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>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-identity-provider.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-identity-provider.html
index 279e598..d6a9dfd 100644
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-identity-provider.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-identity-provider.html
@@ -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>