oidc broker role mapper
This commit is contained in:
parent
edb9f0cecf
commit
a7c563b0eb
17 changed files with 701 additions and 50 deletions
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
230
broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/RoleMapper.java
Executable file
230
broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/RoleMapper.java
Executable file
|
@ -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.";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.broker.oidc.mappers.RoleMapper
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -20,6 +22,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
|
||||
|
|
|
@ -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 : {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -807,7 +807,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{
|
||||
|
@ -1412,3 +1411,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();
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -266,4 +266,33 @@ module.factory('IdentityProviderFactoryLoader', function(Loader, IdentityProvide
|
|||
provider_id: $route.current.params.provider_id
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1002,4 +1002,27 @@ module.factory('IdentityProviderFactory', function($resource) {
|
|||
realm : '@realm',
|
||||
provider_id : '@provider_id'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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'
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue