keycloak-uncached
Changes
broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java 31(+31 -0)
broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 1(+1 -0)
core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java 3(+2 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js 48(+24 -24)
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js 97(+96 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html 86(+86 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html 46(+46 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html 1(+1 -0)
pom.xml 2(+1 -1)
Details
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java
new file mode 100755
index 0000000..15b83e8
--- /dev/null
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java
@@ -0,0 +1,31 @@
+package org.keycloak.broker.provider;
+
+import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper {
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public IdentityProviderMapper create(KeycloakSession session) {
+ return null;
+ }
+
+ @Override
+ public void init(org.keycloak.Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+}
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
index 14626c5..479422e 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
@@ -1,11 +1,14 @@
package org.keycloak.broker.oidc;
import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.services.managers.AuthenticationManager;
@@ -23,6 +26,8 @@ import java.security.PublicKey;
*/
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
+ public static final String VALIDATED_ACCESS_TOKEN = "VALIDATED_ACCESS_TOKEN";
+
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
super(config);
}
@@ -32,6 +37,12 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
return new KeycloakEndpoint(callback, realm, event);
}
+ @Override
+ protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) {
+ JsonWebToken access = validateToken(idpKey, response.getToken());
+ context.getContextData().put(VALIDATED_ACCESS_TOKEN, access);
+ }
+
protected class KeycloakEndpoint extends OIDCEndpoint {
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
super(callback, realm, event);
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/RoleMapper.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/RoleMapper.java
new file mode 100755
index 0000000..ff30b2f
--- /dev/null
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/RoleMapper.java
@@ -0,0 +1,230 @@
+package org.keycloak.broker.oidc.mappers;
+
+import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
+import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
+import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
+import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.IdentityProviderMapperModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.JsonWebToken;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleMapper extends AbstractIdentityProviderMapper {
+
+ public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
+
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ public static final String ROLE = "role";
+ public static final String CLAIM = "claim";
+
+ public static final String ID_TOKEN_CLAIM = "id.token.claim";
+
+ public static final String ACCESS_TOKEN_CLAIM = "access.token.claim";
+
+ public static final String CLAIM_VALUE = "claim.value";
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName(CLAIM);
+ property.setLabel("Claim");
+ property.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
+ property.setType(ProviderConfigProperty.STRING_TYPE);
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(CLAIM_VALUE);
+ property.setLabel("Claim Value");
+ property.setHelpText("Value the claim must have. If the claim is an array, then the value must be contained in the array.");
+ property.setType(ProviderConfigProperty.STRING_TYPE);
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(ID_TOKEN_CLAIM);
+ property.setLabel("ID Token Claim");
+ property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+ property.setDefaultValue("true");
+ property.setHelpText("If this claim is in ID Token, apply role.");
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(ACCESS_TOKEN_CLAIM);
+ property.setLabel("Access Token Claim");
+ property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+ property.setDefaultValue("true");
+ property.setHelpText("If this claim is in Access Token, apply role.");
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(ROLE);
+ property.setLabel("Role");
+ property.setHelpText("Role to grant to user. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
+ property.setType(ProviderConfigProperty.STRING_TYPE);
+ configProperties.add(property);
+ }
+
+ public static final String PROVIDER_ID = "oidc-role-idp-mapper";
+
+ public static String[] parseRole(String role) {
+ int scopeIndex = role.indexOf('.');
+ if (scopeIndex > -1) {
+ String appName = role.substring(0, scopeIndex);
+ role = role.substring(scopeIndex + 1);
+ String[] rtn = {appName, role};
+ return rtn;
+ } else {
+ String[] rtn = {null, role};
+ return rtn;
+
+ }
+ }
+
+ public static Object getClaimValue(JsonWebToken token, String claim) {
+ String[] split = claim.split("\\.");
+ Map<String, Object> jsonObject = token.getOtherClaims();
+ for (int i = 0; i < split.length; i++) {
+ if (i == split.length - 1) {
+ return jsonObject.get(split[i]);
+ } else {
+ Object val = jsonObject.get(split[i]);
+ if (!(val instanceof Map)) return null;
+ jsonObject = (Map<String, Object>)val;
+ }
+ }
+ return null;
+ }
+
+
+
+
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String[] getCompatibleProviders() {
+ return COMPATIBLE_PROVIDERS;
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return "Role Mapper";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Role Mapper";
+ }
+
+ @Override
+ public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+ String roleName = mapperModel.getConfig().get(ROLE);
+ if (isClaimPresent(mapperModel, context)) {
+ RoleModel role = getRoleFromString(realm, roleName);
+ if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
+ user.grantRole(role);
+ }
+ }
+
+ protected RoleModel getRoleFromString(RealmModel realm, String roleName) {
+ String[] parsedRole = parseRole(roleName);
+ RoleModel role = null;
+ if (parsedRole[0] == null) {
+ role = realm.getRole(parsedRole[1]);
+ } else {
+ ClientModel client = realm.getClientByClientId(parsedRole[0]);
+ role = client.getRole(parsedRole[1]);
+ }
+ return role;
+ }
+
+ protected boolean isClaimPresent(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+ boolean searchAccess = Boolean.valueOf(mapperModel.getConfig().get(ACCESS_TOKEN_CLAIM));
+ boolean searchId = Boolean.valueOf(mapperModel.getConfig().get(ID_TOKEN_CLAIM));
+ String claim = mapperModel.getConfig().get(CLAIM);
+ String desiredValue = mapperModel.getConfig().get(CLAIM_VALUE);
+
+ if (searchAccess) {
+ JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
+ if (token != null) {
+ Object value = getClaimValue(token, claim);
+ if (valueEquals(desiredValue, value)) return true;
+ }
+
+ }
+ if (searchId) {
+ JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ID_TOKEN);
+ if (token != null) {
+ Object value = getClaimValue(token, claim);
+ if (valueEquals(desiredValue, value)) return true;
+ }
+
+ }
+ return false;
+ }
+
+ public boolean valueEquals(String desiredValue, Object value) {
+ if (value instanceof String) {
+ if (desiredValue.equals(value)) return true;
+ } else if (value instanceof Double) {
+ try {
+ if (Double.valueOf(desiredValue).equals(value)) return true;
+ } catch (Exception e) {
+
+ }
+ } else if (value instanceof Integer) {
+ try {
+ if (Integer.valueOf(desiredValue).equals(value)) return true;
+ } catch (Exception e) {
+
+ }
+ } else if (value instanceof Boolean) {
+ try {
+ if (Boolean.valueOf(desiredValue).equals(value)) return true;
+ } catch (Exception e) {
+
+ }
+ } else if (value instanceof List) {
+ List list = (List)value;
+ for (Object val : list) {
+ return valueEquals(desiredValue, val);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+ String roleName = mapperModel.getConfig().get(ROLE);
+ if (!isClaimPresent(mapperModel, context)) {
+ RoleModel role = getRoleFromString(realm, roleName);
+ if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
+ user.deleteRoleMapping(role);
+ }
+
+ }
+
+ @Override
+ public String getHelpText() {
+ return "If a claim exists, grant the user the specified realm or application role.";
+ }
+
+}
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index 2d0c16b..745e16d 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -95,13 +95,6 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
}
- protected boolean verify(JWSInput jws, PublicKey key) {
- if (key == null) return true;
- if (!getConfig().isValidateSignature()) return true;
- return RSAProvider.verify(jws, key);
-
- }
-
protected class OIDCEndpoint extends Endpoint {
public OIDCEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
super(callback, realm, event);
@@ -160,6 +153,10 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
return authorizationUrl;
}
+ protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) {
+
+ }
+
@Override
protected BrokeredIdentityContext getFederatedIdentity(String response) {
AccessTokenResponse tokenResponse = null;
@@ -175,7 +172,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
- JsonWebToken idToken = validateIdToken(key, encodedIdToken);
+ JsonWebToken idToken = validateToken(key, encodedIdToken);
try {
String id = idToken.getSubject();
@@ -197,6 +194,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
}
identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse);
identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);
+ processAccessTokenResponse(identity, key, tokenResponse);
identity.setId(id);
identity.setName(name);
@@ -236,23 +234,34 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
return accessToken;
}
- private JsonWebToken validateIdToken(PublicKey key, String encodedToken) {
+ protected boolean verify(JWSInput jws, PublicKey key) {
+ if (key == null) return true;
+ if (!getConfig().isValidateSignature()) return true;
+ return RSAProvider.verify(jws, key);
+
+ }
+
+ protected JsonWebToken validateToken(PublicKey key, String encodedToken) {
if (encodedToken == null) {
- throw new IdentityBrokerException("No id_token from server.");
+ throw new IdentityBrokerException("No token from server.");
}
try {
JWSInput jws = new JWSInput(encodedToken);
if (!verify(jws, key)) {
- throw new IdentityBrokerException("IDToken signature validation failed");
+ throw new IdentityBrokerException("token signature validation failed");
}
- JsonWebToken idToken = jws.readJsonContent(JsonWebToken.class);
+ JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
- String aud = idToken.getAudience();
- String iss = idToken.getIssuer();
+ String aud = token.getAudience();
+ String iss = token.getIssuer();
if (aud != null && !aud.equals(getConfig().getClientId())) {
- throw new IdentityBrokerException("Wrong audience from id_token..");
+ throw new IdentityBrokerException("Wrong audience from token.");
+ }
+
+ if (!token.isActive()) {
+ throw new IdentityBrokerException("Token is no longer valid");
}
String trustedIssuers = getConfig().getIssuer();
@@ -262,15 +271,15 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
for (String trustedIssuer : issuers) {
if (iss != null && iss.equals(trustedIssuer.trim())) {
- return idToken;
+ return token;
}
}
- throw new IdentityBrokerException("Wrong issuer from id_token. Got: " + iss + " expected: " + getConfig().getIssuer());
+ throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
}
- return idToken;
+ return token;
} catch (IOException e) {
- throw new IdentityBrokerException("Could not decode id token.", e);
+ throw new IdentityBrokerException("Could not decode token.", e);
}
}
diff --git a/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
new file mode 100755
index 0000000..98ce5f7
--- /dev/null
+++ b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
@@ -0,0 +1 @@
+org.keycloak.broker.oidc.mappers.RoleMapper
\ No newline at end of file
diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java
index c76f6ee..81fdf0f 100755
--- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java
@@ -1,5 +1,6 @@
package org.keycloak.representations.idm;
+import java.util.LinkedList;
import java.util.List;
/**
@@ -12,7 +13,7 @@ public class IdentityProviderMapperTypeRepresentation {
protected String category;
protected String helpText;
- protected List<ConfigPropertyRepresentation> properties;
+ protected List<ConfigPropertyRepresentation> properties = new LinkedList<>();
public String getId() {
return id;
diff --git a/core/src/test/java/org/keycloak/JsonParserTest.java b/core/src/test/java/org/keycloak/JsonParserTest.java
index 1f51c21..010eae4 100755
--- a/core/src/test/java/org/keycloak/JsonParserTest.java
+++ b/core/src/test/java/org/keycloak/JsonParserTest.java
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
+import java.util.regex.Pattern;
import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonAnySetter;
@@ -12,6 +13,7 @@ import org.codehaus.jackson.annotate.JsonUnwrapped;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.IDToken;
+import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.JsonSerialization;
@@ -21,6 +23,32 @@ import org.keycloak.util.JsonSerialization;
public class JsonParserTest {
@Test
+ public void regex() throws Exception {
+ Pattern p = Pattern.compile(".*(?!\\.pdf)");
+ if (p.matcher("foo.pdf").matches()) {
+ System.out.println(".pdf no match");
+ }
+ if (p.matcher("foo.txt").matches()) {
+ System.out.println("foo.txt matches");
+
+ }
+
+ }
+
+ @Test
+ public void testOtherClaims() throws Exception {
+ String json = "{ \"floatData\" : 555.5," +
+ "\"boolData\": true, " +
+ "\"intData\": 1234," +
+ "\"array\": [ \"val\", \"val2\"] }";
+ JsonWebToken token = JsonSerialization.readValue(json, JsonWebToken.class);
+ System.out.println(token.getOtherClaims().get("floatData").getClass().getName());
+ System.out.println(token.getOtherClaims().get("boolData").getClass().getName());
+ System.out.println(token.getOtherClaims().get("intData").getClass().getName());
+ System.out.println(token.getOtherClaims().get("array").getClass().getName());
+ }
+
+ @Test
public void testUnwrap() throws Exception {
// just experimenting with unwrapped and any properties
IDToken test = new IDToken();
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index 1fe3440..e696714 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -206,6 +206,58 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmIdentityProviderExportCtrl'
})
+ .when('/realms/:realm/identity-provider-mappers/:alias/mappers', {
+ templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mappers.html'; },
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ identityProvider : function(IdentityProviderLoader) {
+ return IdentityProviderLoader();
+ },
+ mapperTypes : function(IdentityProviderMapperTypesLoader) {
+ return IdentityProviderMapperTypesLoader();
+ },
+ mappers : function(IdentityProviderMappersLoader) {
+ return IdentityProviderMappersLoader();
+ }
+ },
+ controller : 'IdentityProviderMapperListCtrl'
+ })
+ .when('/realms/:realm/identity-provider-mappers/:alias/mappers/:mapperId', {
+ templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mapper-detail.html'; },
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ identityProvider : function(IdentityProviderLoader) {
+ return IdentityProviderLoader();
+ },
+ mapperTypes : function(IdentityProviderMapperTypesLoader) {
+ return IdentityProviderMapperTypesLoader();
+ },
+ mapper : function(IdentityProviderMapperLoader) {
+ return IdentityProviderMapperLoader();
+ }
+ },
+ controller : 'IdentityProviderMapperCtrl'
+ })
+ .when('/create/identity-provider-mappers/:realm/:alias', {
+ templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mapper-detail.html'; },
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ identityProvider : function(IdentityProviderLoader) {
+ return IdentityProviderLoader();
+ },
+ mapperTypes : function(IdentityProviderMapperTypesLoader) {
+ return IdentityProviderMapperTypesLoader();
+ }
+ },
+ controller : 'IdentityProviderMapperCreateCtrl'
+ })
+
.when('/realms/:realm/default-roles', {
templateUrl : resourceUrl + '/partials/realm-default-roles.html',
resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 2324d48..13941c3 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1066,30 +1066,6 @@ module.controller('ClientClusteringNodeCtrl', function($scope, client, Client, C
}
});
-module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client, serverInfo,
- ClientProtocolMappersByProtocol,
- $http, $location, Dialog, Notifications) {
- $scope.realm = realm;
- $scope.client = client;
- if (client.protocol == null) {
- client.protocol = 'openid-connect';
- }
-
- var protocolMappers = serverInfo.protocolMapperTypes[client.protocol];
- var mapperTypes = {};
- for (var i = 0; i < protocolMappers.length; i++) {
- mapperTypes[protocolMappers[i].id] = protocolMappers[i];
- }
- $scope.mapperTypes = mapperTypes;
-
-
- var updateMappers = function() {
- $scope.mappers = ClientProtocolMappersByProtocol.query({realm : realm.realm, client : client.id, protocol : client.protocol});
- };
-
- updateMappers();
-});
-
module.controller('AddBuiltinProtocolMapperCtrl', function($scope, realm, client, serverInfo,
ClientProtocolMappersByProtocol,
$http, $location, Dialog, Notifications) {
@@ -1152,6 +1128,30 @@ module.controller('AddBuiltinProtocolMapperCtrl', function($scope, realm, client
});
+module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client, serverInfo,
+ ClientProtocolMappersByProtocol,
+ $http, $location, Dialog, Notifications) {
+ $scope.realm = realm;
+ $scope.client = client;
+ if (client.protocol == null) {
+ client.protocol = 'openid-connect';
+ }
+
+ var protocolMappers = serverInfo.protocolMapperTypes[client.protocol];
+ var mapperTypes = {};
+ for (var i = 0; i < protocolMappers.length; i++) {
+ mapperTypes[protocolMappers[i].id] = protocolMappers[i];
+ }
+ $scope.mapperTypes = mapperTypes;
+
+
+ var updateMappers = function() {
+ $scope.mappers = ClientProtocolMappersByProtocol.query({realm : realm.realm, client : client.id, protocol : client.protocol});
+ };
+
+ updateMappers();
+});
+
module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, mapper, ClientProtocolMapper, Notifications, Dialog, $location) {
$scope.realm = realm;
$scope.client = client;
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 72c3f79..c5479e0 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -803,7 +803,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
});
};
$scope.$watch('fromUrl.data', function(newVal, oldVal){
- console.log('watch fromUrl: ' + newVal + " " + oldVal);
if ($scope.fromUrl.data && $scope.fromUrl.data.length > 0) {
$scope.importUrl = true;
} else{
@@ -1408,3 +1407,99 @@ module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $
});
+module.controller('IdentityProviderMapperListCtrl', function($scope, realm, identityProvider, mapperTypes, mappers) {
+ $scope.realm = realm;
+ $scope.identityProvider = identityProvider;
+ $scope.mapperTypes = mapperTypes;
+ $scope.mappers = mappers;
+});
+
+module.controller('IdentityProviderMapperCtrl', function($scope, realm, identityProvider, mapperTypes, mapper, IdentityProviderMapper, Notifications, Dialog, $location) {
+ $scope.realm = realm;
+ $scope.identityProvider = identityProvider;
+ $scope.create = false;
+ $scope.mapper = angular.copy(mapper);
+ $scope.changed = false;
+ $scope.mapperType = mapperTypes[mapper.identityProviderMapper];
+ $scope.$watch(function() {
+ return $location.path();
+ }, function() {
+ $scope.path = $location.path().substring(1).split("/");
+ });
+
+ $scope.$watch('mapper', function() {
+ if (!angular.equals($scope.mapper, mapper)) {
+ $scope.changed = true;
+ }
+ }, true);
+
+ $scope.save = function() {
+ IdentityProviderMapper.update({
+ realm : realm.realm,
+ client: client.id,
+ mapperId : mapper.id
+ }, $scope.mapper, function() {
+ $scope.changed = false;
+ mapper = angular.copy($scope.mapper);
+ $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers/" + mapper.id);
+ Notifications.success("Your changes have been saved.");
+ });
+ };
+
+ $scope.reset = function() {
+ $scope.mapper = angular.copy(mapper);
+ $scope.changed = false;
+ };
+
+ $scope.cancel = function() {
+ //$location.url("/realms");
+ window.history.back();
+ };
+
+ $scope.remove = function() {
+ Dialog.confirmDelete($scope.mapper.name, 'mapper', function() {
+ IdentityProviderMapper.remove({ realm: realm.realm, alias: mapper.identityProviderAlias, mapperId : $scope.mapper.id }, function() {
+ Notifications.success("The mapper has been deleted.");
+ $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers");
+ });
+ });
+ };
+
+});
+
+module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, identityProvider, mapperTypes, IdentityProviderMapper, Notifications, Dialog, $location) {
+ $scope.realm = realm;
+ $scope.identityProvider = identityProvider;
+ $scope.create = true;
+ $scope.mapper = { identityProviderAlias: identityProvider.alias, config: {}};
+ $scope.mapperTypes = mapperTypes;
+
+ $scope.$watch(function() {
+ return $location.path();
+ }, function() {
+ $scope.path = $location.path().substring(1).split("/");
+ });
+
+ $scope.save = function() {
+ $scope.mapper.identityProviderMapper = $scope.mapperType.id;
+ IdentityProviderMapper.save({
+ realm : realm.realm, alias: identityProvider.alias
+ }, $scope.mapper, function(data, headers) {
+ var l = headers().location;
+ var id = l.substring(l.lastIndexOf("/") + 1);
+ $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers/" + id);
+ Notifications.success("Mapper has been created.");
+ });
+ };
+
+ $scope.cancel = function() {
+ //$location.url("/realms");
+ window.history.back();
+ };
+
+
+});
+
+
+
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index b6e0541..bf36307 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -266,4 +266,33 @@ module.factory('IdentityProviderFactoryLoader', function(Loader, IdentityProvide
provider_id: $route.current.params.provider_id
}
});
-});
\ No newline at end of file
+});
+
+module.factory('IdentityProviderMapperTypesLoader', function(Loader, IdentityProviderMapperTypes, $route, $q) {
+ return Loader.get(IdentityProviderMapperTypes, function () {
+ return {
+ realm: $route.current.params.realm,
+ alias: $route.current.params.alias
+ }
+ });
+});
+
+module.factory('IdentityProviderMappersLoader', function(Loader, IdentityProviderMappers, $route, $q) {
+ return Loader.query(IdentityProviderMappers, function () {
+ return {
+ realm: $route.current.params.realm,
+ alias: $route.current.params.alias
+ }
+ });
+});
+
+module.factory('IdentityProviderMapperLoader', function(Loader, IdentityProviderMapper, $route, $q) {
+ return Loader.get(IdentityProviderMapper, function () {
+ return {
+ realm: $route.current.params.realm,
+ alias: $route.current.params.alias,
+ mapperId: $route.current.params.mapperId
+ }
+ });
+});
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index 3d1cd8d..6703973 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -1004,4 +1004,27 @@ module.factory('IdentityProviderFactory', function($resource) {
realm : '@realm',
provider_id : '@provider_id'
});
-});
\ No newline at end of file
+});
+
+module.factory('IdentityProviderMapperTypes', function($resource) {
+ return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mapper-types', {
+ realm : '@realm',
+ alias : '@alias'
+ });
+});
+
+module.factory('IdentityProviderMappers', function($resource) {
+ return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mappers', {
+ realm : '@realm',
+ alias : '@alias'
+ });
+});
+
+module.factory('IdentityProviderMapper', function($resource) {
+ return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mappers/:mapperId', {
+ realm : '@realm',
+ alias : '@alias',
+ mapperId: '@mapperId'
+ });
+});
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html
new file mode 100755
index 0000000..4e1fdc9
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html
@@ -0,0 +1,86 @@
+<div class="bs-sidebar col-sm-3 " data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
+<div id="content-area" class="col-sm-9" role="main">
+ <kc-navigation-client></kc-navigation-client>
+ <div id="content">
+ <ol class="breadcrumb" data-ng-show="create">
+ <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
+ <li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{identityProvider.alias}} Provider</a></li>
+ <li><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Identity Provider Mappers</a></li>
+ <li class="active">Create IdentityProvider Mapper</li>
+ </ol>
+
+ <ol class="breadcrumb" data-ng-hide="create">
+ <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
+ <li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{identityProvider.alias}} Provider</a></li>
+ <li><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Identity Provider Mappers</a></li>
+ <li class="active">{{mapper.name}}</li>
+ </ol>
+ <h2 class="pull-left" data-ng-hide="create">{{mapper.name}} Identity Provider Mapper</h2>
+ <h2 class="pull-left" data-ng-show="create">Create Identity Provider Mapper</h2>
+ <p class="subtitle"><span class="required">*</span> Required fields</p>
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+
+ <fieldset>
+ <div class="form-group clearfix" data-ng-show="!create">
+ <label class="col-sm-2 control-label" for="mapperId">ID </label>
+ <div class="col-sm-4">
+ <input class="form-control" id="mapperId" type="text" ng-model="mapper.id" readonly>
+ </div>
+ </div>
+ <div class="form-group clearfix">
+ <label class="col-sm-2 control-label" for="name">Name <span class="required">*</span></label>
+ <div class="col-sm-4">
+ <input class="form-control" id="name" type="text" ng-model="mapper.name" data-ng-readonly="!create" required>
+ </div>
+ <span tooltip-placement="right" tooltip="Name of the mapper." class="fa fa-info-circle"></span>
+ </div>
+ <div class="form-group" data-ng-show="create">
+ <label class="col-sm-2 control-label" for="mapperTypeCreate">Mapper Type</label>
+ <div class="col-sm-6">
+ <div class="select-kc">
+ <select id="mapperTypeCreate"
+ ng-model="mapperType"
+ ng-options="mapperType.name for (mapperKey, mapperType) in mapperTypes">
+ </select>
+ </div>
+ </div>
+ <span tooltip-placement="right" tooltip="{{mapperType.helpText}}" class="fa fa-info-circle"></span>
+ </div>
+ <div class="form-group clearfix" data-ng-hide="create">
+ <label class="col-sm-2 control-label" for="mapperType">Mapper Type</label>
+ <div class="col-sm-4">
+ <input class="form-control" id="mapperType" type="text" ng-model="mapperType.name" data-ng-readonly="true">
+ </div>
+ <span tooltip-placement="right" tooltip="{{mapperType.helpText}}" class="fa fa-info-circle"></span>
+ </div>
+ <div data-ng-repeat="option in mapperType.properties" class="form-group">
+ <label class="col-sm-2 control-label">{{option.label}}</label>
+
+ <div class="col-sm-4" data-ng-hide="option.type == 'boolean' || option.type == 'List'">
+ <input class="form-control" type="text" data-ng-model="mapper.config[ option.name ]" >
+ </div>
+ <div class="col-sm-4" data-ng-show="option.type == 'boolean'">
+ <input ng-model="mapper.config[ option.name ]" value="'true'" name="option.name" id="option.name" onoffswitchmodel />
+ </div>
+ <div class="col-sm-4" data-ng-show="option.type == 'List'">
+ <select ng-model="mapper.config[ option.name ]" ng-options="data for data in option.defaultValue">
+ <option value="" selected> Select one... </option>
+ </select>
+ </div>
+ <span tooltip-placement="right" tooltip="{{option.helpText}}" class="fa fa-info-circle"></span>
+ </div>
+
+ </fieldset>
+ <div class="pull-right form-actions" data-ng-show="create && access.manageRealm">
+ <button kc-cancel data-ng-click="cancel()">Cancel</button>
+ <button kc-save>Save</button>
+ </div>
+
+ <div class="pull-right form-actions" data-ng-show="!create && access.manageRealm">
+ <button kc-reset data-ng-show="changed">Clear changes</button>
+ <button kc-save data-ng-show="changed">Save</button>
+ <button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
+ </div>
+ </form>
+ </div>
+</div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html
new file mode 100755
index 0000000..2449609
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html
@@ -0,0 +1,46 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+ <kc-navigation-client></kc-navigation-client>
+ <div id="content">
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
+ <li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{identityProvider.alias}} Provider</a></li>
+ <li class="active">{{identityProvider.alias}} Mappers</li>
+ </ol>
+ <h2><span>{{realm.realm}} </span> {{identityProvider.alias}} Identity Provider Mappers <span tooltip-placement="right" tooltip="Identity Provider Mappers perform transformation on tokens and documents. They an do things like map external tokens and claims into role grants and user attributes." class="fa fa-info-circle"></span></h2>
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th class="kc-table-actions" colspan="4">
+ <div class="search-comp clearfix">
+ <input type="text" placeholder="Search..." class="form-control search" data-ng-model="search.name"
+ onkeyup="if(event.keyCode == 13){$(this).next('button').click();}">
+ <button type="submit" class="kc-icon-search" tooltip-placement="right"
+ tooltip="Search by mapper name.">
+ Icon: search
+ </button>
+ </div>
+ <div class="pull-right">
+ <a class="btn btn-primary" href="#/create/identity-provider-mappers/{{realm.realm}}/{{identityProvider.alias}}">Create</a>
+ </div>
+ </th>
+ </tr>
+ <tr data-ng-hide="mappers.length == 0">
+ <th>Name</th>
+ <th>Category</th>
+ <th>Type</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="mapper in mappers | filter:search">
+ <td><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
+ <td>{{mapperTypes[mapper.identityProviderMapper].category}}</td>
+ <td>{{mapperTypes[mapper.identityProviderMapper].name}}</td>
+ </tr>
+ <tr data-ng-show="mappers.length == 0">
+ <td>No mappers available</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
index 67b0a68..0d1d027 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
@@ -185,6 +185,7 @@
</fieldset>
<div class="pull-right form-actions">
+ <a data-ng-show="!newIdentityProvider" class="btn btn-lg btn-primary" href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Mappers</a>
<button kc-save data-ng-show="changed">Save</button>
<button type="submit" data-ng-click="cancel()" data-ng-show="changed" class="btn btn-lg btn-default">Cancel</button>
<button kc-delete data-ng-click="remove()" data-ng-show="!newIdentityProvider">Delete</button>
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 87ac607..326ec90 100755
--- a/pom.xml
+++ b/pom.xml
@@ -588,7 +588,7 @@
<version>2.16</version>
<configuration>
<forkMode>once</forkMode>
- <argLine>-Xms512m -Xmx512m -XX:MaxPermSize=256m</argLine>
+ <argLine>-Xms512m -Xmx1024m -XX:MaxPermSize=512m</argLine>
</configuration>
</plugin>
<plugin>
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index fcc1d63..35cce00 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -43,8 +43,10 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
/**
* @author Pedro Igor
@@ -194,10 +196,10 @@ public class IdentityProviderResource {
@GET
@Path("mapper-types")
@NoCache
- public List<IdentityProviderMapperTypeRepresentation> getMapperTypes() {
+ public Map<String, IdentityProviderMapperTypeRepresentation> getMapperTypes() {
this.auth.requireView();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
- List<IdentityProviderMapperTypeRepresentation> types = new LinkedList<>();
+ Map<String, IdentityProviderMapperTypeRepresentation> types = new HashMap<>();
List<ProviderFactory> factories = sessionFactory.getProviderFactories(IdentityProviderMapper.class);
for (ProviderFactory factory : factories) {
IdentityProviderMapper mapper = (IdentityProviderMapper)factory;
@@ -218,7 +220,7 @@ public class IdentityProviderResource {
propRep.setHelpText(prop.getHelpText());
rep.getProperties().add(propRep);
}
- types.add(rep);
+ types.put(rep.getId(), rep);
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 91ced7f..1d99e1f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -24,6 +24,7 @@ package org.keycloak.testsuite.account;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@@ -156,6 +157,11 @@ public class AccountTest {
});
}
+ //@Test
+ public void ideTesting() throws Exception {
+ Thread.sleep(100000000);
+ }
+
@Test
public void returnToAppFromQueryParam() {
driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");