Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
bab08bf8f0
36 changed files with 494 additions and 129 deletions
|
@ -82,8 +82,12 @@
|
|||
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $inf)]">
|
||||
<xsl:copy>
|
||||
<cache-container name="keycloak" jndi-name="infinispan/Keycloak">
|
||||
<local-cache name="realms"/>
|
||||
<local-cache name="users"/>
|
||||
<local-cache name="realms">
|
||||
<eviction max-entries="10000" strategy="LRU"/>
|
||||
</local-cache>
|
||||
<local-cache name="users">
|
||||
<eviction max-entries="10000" strategy="LRU"/>
|
||||
</local-cache>
|
||||
<local-cache name="sessions"/>
|
||||
<local-cache name="offlineSessions"/>
|
||||
<local-cache name="loginFailures"/>
|
||||
|
|
|
@ -3,6 +3,7 @@ embed-server --server-config=standalone-ha.xml
|
|||
/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak")
|
||||
/subsystem=infinispan/cache-container=keycloak/transport=TRANSPORT:add(lock-timeout=60000)
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add()
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=realms/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=users/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
|
||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
|
||||
|
|
|
@ -2,6 +2,7 @@ embed-server --server-config=standalone.xml
|
|||
/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",jta=false,driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true)
|
||||
/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak")
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add()
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=realms/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=users/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add()
|
||||
|
|
|
@ -421,7 +421,12 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
|
||||
// Check here if user already exists
|
||||
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
|
||||
if (session.userLocalStorage().getUserByUsername(ldapUsername, realm) != null) {
|
||||
UserModel user = session.userLocalStorage().getUserByUsername(ldapUsername, realm);
|
||||
|
||||
if (user != null) {
|
||||
LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig());
|
||||
// If email attribute mapper is set to "Always Read Value From LDAP" the user may be in Keycloak DB with an old email address
|
||||
if (ldapUser.getUuid().equals(user.getFirstAttribute(LDAPConstants.LDAP_ID))) return user;
|
||||
throw new ModelDuplicateException("User with username '" + ldapUsername + "' already exists in Keycloak. It conflicts with LDAP user with email '" + email + "'");
|
||||
}
|
||||
|
||||
|
@ -483,9 +488,20 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
UserCredentialModel cred = (UserCredentialModel)input;
|
||||
String password = cred.getValue();
|
||||
LDAPObject ldapUser = loadAndValidateUser(realm, user);
|
||||
|
||||
try {
|
||||
ldapIdentityStore.updatePassword(ldapUser, password);
|
||||
if (updater != null) updater.passwordUpdated(user, ldapUser, input);
|
||||
return true;
|
||||
} catch (ModelException me) {
|
||||
if (updater != null) {
|
||||
updater.passwordUpdateFailed(user, ldapUser, input, me);
|
||||
return false;
|
||||
} else {
|
||||
throw me;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.storage.ldap.mappers;
|
||||
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
|
||||
|
@ -25,5 +26,8 @@ import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface PasswordUpdated {
|
||||
|
||||
void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input);
|
||||
|
||||
void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, CredentialInput input, ModelException exception) throws ModelException;
|
||||
}
|
||||
|
|
|
@ -89,6 +89,11 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
|
|||
updateUserAccountControl(ldapUser, control);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, CredentialInput input, ModelException exception) {
|
||||
throw processFailedPasswordUpdateException(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel proxy(LDAPObject ldapUser, UserModel delegate) {
|
||||
return new MSADUserModelDelegate(delegate, ldapUser);
|
||||
|
|
|
@ -88,6 +88,11 @@ public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageM
|
|||
ldapProvider.getLdapIdentityStore().update(ldapUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, CredentialInput input, ModelException exception) {
|
||||
throw processFailedPasswordUpdateException(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel proxy(LDAPObject ldapUser, UserModel delegate) {
|
||||
return new MSADUserModelDelegate(delegate, ldapUser);
|
||||
|
|
|
@ -103,15 +103,20 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||
containerManaged = true;
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, getRevisionCacheConfig(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_DEFAULT_MAX));
|
||||
cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME, true);
|
||||
long realmRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
|
||||
realmRevisionsMaxEntries = realmRevisionsMaxEntries > 0
|
||||
? 2 * realmRevisionsMaxEntries
|
||||
: InfinispanConnectionProvider.REALM_REVISIONS_CACHE_DEFAULT_MAX;
|
||||
|
||||
long maxEntries = cacheManager.getCache(InfinispanConnectionProvider.USER_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
|
||||
if (maxEntries <= 0) {
|
||||
maxEntries = InfinispanConnectionProvider.USER_REVISIONS_CACHE_DEFAULT_MAX;
|
||||
}
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, getRevisionCacheConfig(realmRevisionsMaxEntries));
|
||||
cacheManager.getCache(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, true);
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, getRevisionCacheConfig(maxEntries));
|
||||
long userRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.USER_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
|
||||
userRevisionsMaxEntries = userRevisionsMaxEntries > 0
|
||||
? 2 * userRevisionsMaxEntries
|
||||
: InfinispanConnectionProvider.USER_REVISIONS_CACHE_DEFAULT_MAX;
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, getRevisionCacheConfig(userRevisionsMaxEntries));
|
||||
cacheManager.getCache(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, true);
|
||||
cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME, true);
|
||||
cacheManager.getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME, true);
|
||||
|
@ -189,15 +194,20 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
|
||||
counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, getRevisionCacheConfig(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_DEFAULT_MAX));
|
||||
cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME, true);
|
||||
long realmRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
|
||||
realmRevisionsMaxEntries = realmRevisionsMaxEntries > 0
|
||||
? 2 * realmRevisionsMaxEntries
|
||||
: InfinispanConnectionProvider.REALM_REVISIONS_CACHE_DEFAULT_MAX;
|
||||
|
||||
long maxEntries = cacheManager.getCache(InfinispanConnectionProvider.USER_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
|
||||
if (maxEntries <= 0) {
|
||||
maxEntries = InfinispanConnectionProvider.USER_REVISIONS_CACHE_DEFAULT_MAX;
|
||||
}
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, getRevisionCacheConfig(realmRevisionsMaxEntries));
|
||||
cacheManager.getCache(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, true);
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, getRevisionCacheConfig(maxEntries));
|
||||
long userRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.USER_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
|
||||
userRevisionsMaxEntries = userRevisionsMaxEntries > 0
|
||||
? 2 * userRevisionsMaxEntries
|
||||
: InfinispanConnectionProvider.USER_REVISIONS_CACHE_DEFAULT_MAX;
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, getRevisionCacheConfig(userRevisionsMaxEntries));
|
||||
cacheManager.getCache(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, true);
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.KEYS_CACHE_NAME, getKeysCacheConfig());
|
||||
|
|
|
@ -27,7 +27,7 @@ public interface InfinispanConnectionProvider extends Provider {
|
|||
|
||||
String REALM_CACHE_NAME = "realms";
|
||||
String REALM_REVISIONS_CACHE_NAME = "realmRevisions";
|
||||
int REALM_REVISIONS_CACHE_DEFAULT_MAX = 10000;
|
||||
int REALM_REVISIONS_CACHE_DEFAULT_MAX = 20000;
|
||||
|
||||
String USER_CACHE_NAME = "users";
|
||||
String USER_REVISIONS_CACHE_NAME = "userRevisions";
|
||||
|
|
|
@ -38,6 +38,7 @@ public class RoleAdapter implements RoleModel {
|
|||
protected CachedRole cached;
|
||||
protected RealmCacheSession cacheSession;
|
||||
protected RealmModel realm;
|
||||
protected Set<RoleModel> composites;
|
||||
|
||||
public RoleAdapter(CachedRole cached, RealmCacheSession session, RealmModel realm) {
|
||||
this.cached = cached;
|
||||
|
@ -132,15 +133,19 @@ public class RoleAdapter implements RoleModel {
|
|||
@Override
|
||||
public Set<RoleModel> getComposites() {
|
||||
if (isUpdated()) return updated.getComposites();
|
||||
Set<RoleModel> set = new HashSet<RoleModel>();
|
||||
|
||||
if (composites == null) {
|
||||
composites = new HashSet<RoleModel>();
|
||||
for (String id : cached.getComposites()) {
|
||||
RoleModel role = realm.getRoleById(id);
|
||||
if (role == null) {
|
||||
throw new IllegalStateException("Could not find composite in role " + getName() + ": " + id);
|
||||
}
|
||||
set.add(role);
|
||||
composites.add(role);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
return composites;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -171,11 +176,7 @@ public class RoleAdapter implements RoleModel {
|
|||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (this.equals(role)) return true;
|
||||
if (!isComposite()) return false;
|
||||
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
return KeycloakModelUtils.searchFor(role, this, visited);
|
||||
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -128,11 +128,7 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
|
|||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (this.equals(role)) return true;
|
||||
if (!isComposite()) return false;
|
||||
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
return KeycloakModelUtils.searchFor(role, this, visited);
|
||||
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
</addColumn>
|
||||
<sql>UPDATE COMPONENT_CONFIG SET VALUE_NEW = VALUE, VALUE = NULL</sql>
|
||||
<dropColumn tableName="COMPONENT_CONFIG" columnName="VALUE"/>
|
||||
<renameColumn tableName="COMPONENT_CONFIG" oldColumnName="VALUE_NEW" newColumnName="VALUE"/>
|
||||
<renameColumn tableName="COMPONENT_CONFIG" oldColumnName="VALUE_NEW" newColumnName="VALUE" columnDataType="NCLOB"/>
|
||||
<!--
|
||||
<modifyDataType tableName="COMPONENT_CONFIG" columnName="VALUE" newDataType="NVARCHAR(2000)"/>
|
||||
-->
|
||||
|
|
|
@ -172,11 +172,7 @@ public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> implement
|
|||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (this.equals(role)) return true;
|
||||
if (!isComposite()) return false;
|
||||
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
return KeycloakModelUtils.searchFor(role, this, visited);
|
||||
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
|
||||
}
|
||||
|
||||
public MongoRoleEntity getRole() {
|
||||
|
|
|
@ -320,6 +320,8 @@ public class SAMLParserUtil {
|
|||
return StaxParserUtil.getElementText(xmlEventReader);
|
||||
} else if(typeValue.contains(":base64Binary")){
|
||||
return StaxParserUtil.getElementText(xmlEventReader);
|
||||
} else if(typeValue.contains(":boolean")){
|
||||
return StaxParserUtil.getElementText(xmlEventReader);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -177,16 +177,14 @@ public final class KeycloakModelUtils {
|
|||
* @param visited set of already visited roles (used for recursion)
|
||||
* @return true if "role" is descendant of "composite"
|
||||
*/
|
||||
public static boolean searchFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
|
||||
if (visited.contains(composite)) return false;
|
||||
visited.add(composite);
|
||||
Set<RoleModel> composites = composite.getComposites();
|
||||
if (composites.contains(role)) return true;
|
||||
for (RoleModel contained : composites) {
|
||||
if (!contained.isComposite()) continue;
|
||||
if (searchFor(role, contained, visited)) return true;
|
||||
}
|
||||
return false;
|
||||
public static boolean searchFor(RoleModel role, RoleModel composite) {
|
||||
return composite.isComposite() && (
|
||||
composite.getComposites().contains(role) ||
|
||||
composite.getComposites().stream()
|
||||
.filter(x -> x.isComposite() && x.hasRole(role))
|
||||
.findFirst()
|
||||
.isPresent()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,7 +20,11 @@ package org.keycloak.models.utils;
|
|||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
|
@ -98,4 +102,33 @@ public class RoleUtils {
|
|||
.anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively expands composite roles into their composite.
|
||||
* @param role
|
||||
* @return Stream of containing all of the composite roles and their components.
|
||||
*/
|
||||
public static Stream<RoleModel> expandCompositeRolesStream(RoleModel role) {
|
||||
Stream.Builder<RoleModel> sb = Stream.builder();
|
||||
Set<RoleModel> roles = new HashSet<>();
|
||||
|
||||
Deque<RoleModel> stack = new ArrayDeque<>();
|
||||
stack.add(role);
|
||||
|
||||
while (! stack.isEmpty()) {
|
||||
RoleModel current = stack.pop();
|
||||
sb.add(current);
|
||||
|
||||
if (current.isComposite()) {
|
||||
current.getComposites().stream()
|
||||
.filter(r -> ! roles.contains(r))
|
||||
.forEach(r -> {
|
||||
roles.add(r);
|
||||
stack.add(r);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return sb.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ import java.util.Map;
|
|||
* </ol>
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the {@code user} variable is only defined when the user was identified by a preceeding
|
||||
* authentication step, e.g. by the {@link UsernamePasswordForm} authenticator.
|
||||
* </p>
|
||||
* <p>
|
||||
* Additional context information can be extracted from the {@code context} argument passed to the {@code authenticate(context)}
|
||||
* or {@code action(context)} function.
|
||||
* <p>
|
||||
|
@ -63,9 +67,10 @@ import java.util.Map;
|
|||
*
|
||||
* function authenticate(context) {
|
||||
*
|
||||
* LOG.info(script.name + " --> trace auth for: " + user.username);
|
||||
* var username = user ? user.username : "anonymous";
|
||||
* LOG.info(script.name + " --> trace auth for: " + username);
|
||||
*
|
||||
* if ( user.username === "tester"
|
||||
* if ( username === "tester"
|
||||
* && user.getAttribute("someAttribute")
|
||||
* && user.getAttribute("someAttribute").contains("someValue")) {
|
||||
*
|
||||
|
@ -160,7 +165,7 @@ public class ScriptBasedAuthenticator implements Authenticator {
|
|||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,10 +22,9 @@ import org.keycloak.models.ProtocolMapperModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -54,7 +53,7 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper
|
|||
user.getGroups().stream()
|
||||
.flatMap(g -> groupAndItsParentsStream(g))
|
||||
.flatMap(g -> g.getRoleMappings().stream()))
|
||||
.flatMap(role -> expandCompositeRolesStream(role));
|
||||
.flatMap(RoleUtils::expandCompositeRolesStream);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,29 +70,6 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper
|
|||
return sb.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively expands composite roles into their composite.
|
||||
* @param role
|
||||
* @return Stream of containing all of the composite roles and their components.
|
||||
*/
|
||||
private static Stream<RoleModel> expandCompositeRolesStream(RoleModel role) {
|
||||
Stream.Builder<RoleModel> sb = Stream.builder();
|
||||
|
||||
Deque<RoleModel> stack = new ArrayDeque<>();
|
||||
stack.add(role);
|
||||
|
||||
while (! stack.isEmpty()) {
|
||||
RoleModel current = stack.pop();
|
||||
sb.add(current);
|
||||
|
||||
if (current.isComposite()) {
|
||||
stack.addAll(current.getComposites());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all roles of the current user based on direct roles set to the user, its groups and their parent groups.
|
||||
* Then it recursively expands all composite roles, and restricts according to the given predicate {@code restriction}.
|
||||
|
|
|
@ -248,9 +248,9 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
String bindingType = getBindingType(requestAbstractType);
|
||||
if (samlClient.forcePostBinding())
|
||||
bindingType = SamlProtocol.SAML_POST_BINDING;
|
||||
String redirect = null;
|
||||
String redirect;
|
||||
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
|
||||
if (redirectUri != null && !"null".equals(redirectUri)) { // "null" is for testing purposes
|
||||
if (redirectUri != null && ! "null".equals(redirectUri.toString())) { // "null" is for testing purposes
|
||||
redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
|
||||
} else {
|
||||
if (bindingType.equals(SamlProtocol.SAML_POST_BINDING)) {
|
||||
|
@ -279,8 +279,9 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
|
||||
// Handle NameIDPolicy from SP
|
||||
NameIDPolicyType nameIdPolicy = requestAbstractType.getNameIDPolicy();
|
||||
if (nameIdPolicy != null && !samlClient.forceNameIDFormat()) {
|
||||
String nameIdFormat = nameIdPolicy.getFormat().toString();
|
||||
final URI nameIdFormatUri = nameIdPolicy == null ? null : nameIdPolicy.getFormat();
|
||||
if (nameIdFormatUri != null && ! samlClient.forceNameIDFormat()) {
|
||||
String nameIdFormat = nameIdFormatUri.toString();
|
||||
// TODO: Handle AllowCreate too, relevant for persistent NameID.
|
||||
if (isSupportedNameIdFormat(nameIdFormat)) {
|
||||
clientSession.setNote(GeneralConstants.NAMEID_FORMAT, nameIdFormat);
|
||||
|
@ -345,7 +346,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
|
||||
if (authResult != null) {
|
||||
String logoutBinding = getBindingType();
|
||||
if ("true".equals(samlClient.forcePostBinding()))
|
||||
if (samlClient.forcePostBinding())
|
||||
logoutBinding = SamlProtocol.SAML_POST_BINDING;
|
||||
boolean postBinding = Objects.equals(SamlProtocol.SAML_POST_BINDING, logoutBinding);
|
||||
|
||||
|
|
|
@ -35,12 +35,13 @@ import java.util.Map;
|
|||
public class HardcodedRole extends AbstractSAMLProtocolMapper {
|
||||
public static final String PROVIDER_ID = "saml-hardcode-role-mapper";
|
||||
public static final String ATTRIBUTE_VALUE = "attribute.value";
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||
public static final String ROLE_ATTRIBUTE = "role";
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName("role");
|
||||
property.setName(ROLE_ATTRIBUTE);
|
||||
property.setLabel("Role");
|
||||
property.setHelpText("Arbitrary role name you want to hardcode. This role does not have to exist in current realm and can be just any string you need");
|
||||
property.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||
|
@ -79,8 +80,8 @@ public class HardcodedRole extends AbstractSAMLProtocolMapper {
|
|||
mapper.setName(name);
|
||||
mapper.setProtocolMapper(mapperId);
|
||||
mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
|
||||
Map<String, String> config = new HashMap<String, String>();
|
||||
config.put("role", role);
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put(ROLE_ATTRIBUTE, role);
|
||||
mapper.setConfig(config);
|
||||
return mapper;
|
||||
|
||||
|
|
|
@ -23,8 +23,9 @@ import org.keycloak.models.ClientSessionModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.protocol.ProtocolMapper;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
@ -35,7 +36,9 @@ import java.util.HashMap;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -45,7 +48,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
|
|||
public static final String PROVIDER_ID = "saml-role-list-mapper";
|
||||
public static final String SINGLE_ROLE_ATTRIBUTE = "single";
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
|
@ -120,11 +123,13 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
|
|||
|
||||
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
|
||||
if (mapper == null) continue;
|
||||
|
||||
if (mapper instanceof SAMLRoleNameMapper) {
|
||||
roleNameMappers.add(new SamlProtocol.ProtocolMapperProcessor<>((SAMLRoleNameMapper) mapper,mapping));
|
||||
}
|
||||
|
||||
if (mapper instanceof HardcodedRole) {
|
||||
AttributeType attributeType = null;
|
||||
AttributeType attributeType;
|
||||
if (singleAttribute) {
|
||||
if (singleAttributeType == null) {
|
||||
singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
|
||||
|
@ -135,14 +140,26 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
|
|||
attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
|
||||
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
|
||||
}
|
||||
attributeType.addAttributeValue(mapping.getConfig().get("role"));
|
||||
|
||||
attributeType.addAttributeValue(mapping.getConfig().get(HardcodedRole.ROLE_ATTRIBUTE));
|
||||
}
|
||||
}
|
||||
|
||||
for (String roleId : clientSession.getRoles()) {
|
||||
RealmModel realm = clientSession.getRealm();
|
||||
List<String> allRoleNames = clientSession.getRoles().stream()
|
||||
// todo need a role mapping
|
||||
RoleModel roleModel = clientSession.getRealm().getRoleById(roleId);
|
||||
AttributeType attributeType = null;
|
||||
.map(realm::getRoleById)
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(RoleUtils::expandCompositeRolesStream)
|
||||
.map(roleModel -> roleNameMappers.stream()
|
||||
.map(entry -> entry.mapper.mapName(entry.model, roleModel))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(roleModel.getName())
|
||||
).collect(Collectors.toList());
|
||||
|
||||
for (String roleName : allRoleNames) {
|
||||
AttributeType attributeType;
|
||||
if (singleAttribute) {
|
||||
if (singleAttributeType == null) {
|
||||
singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
|
||||
|
@ -153,14 +170,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
|
|||
attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
|
||||
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
|
||||
}
|
||||
String roleName = roleModel.getName();
|
||||
for (SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper> entry : roleNameMappers) {
|
||||
String newName = entry.mapper.mapName(entry.model, roleModel);
|
||||
if (newName != null) {
|
||||
roleName = newName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributeType.addAttributeValue(roleName);
|
||||
}
|
||||
|
||||
|
@ -172,7 +182,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
|
|||
mapper.setProtocolMapper(PROVIDER_ID);
|
||||
mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
|
||||
mapper.setConsentRequired(false);
|
||||
Map<String, String> config = new HashMap<String, String>();
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, samlAttributeName);
|
||||
if (friendlyName != null) {
|
||||
config.put(AttributeStatementHelper.FRIENDLY_NAME, friendlyName);
|
||||
|
|
|
@ -24,7 +24,8 @@ AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationF
|
|||
*/
|
||||
function authenticate(context) {
|
||||
|
||||
LOG.info(script.name + " trace auth for: " + user.username);
|
||||
var username = user ? user.username : "anonymous";
|
||||
LOG.info(script.name + " trace auth for: " + username);
|
||||
|
||||
var authShouldFail = false;
|
||||
if (authShouldFail) {
|
||||
|
|
|
@ -7,7 +7,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
|||
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
|
||||
import org.keycloak.testsuite.adapter.page.HawtioPage;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
|
||||
|
@ -17,7 +16,7 @@ import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class AbstractHawtioAdapterTest extends AbstractExampleAdapterTest {
|
||||
public abstract class AbstractHawtioAdapterTest extends AbstractExampleAdapterTest {
|
||||
|
||||
@Page
|
||||
private HawtioPage hawtioPage;
|
||||
|
|
|
@ -72,7 +72,7 @@ import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
|||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTest {
|
||||
public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTest {
|
||||
|
||||
@Page
|
||||
private SecurePortal securePortal;
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.jboss.arquillian.graphene.page.Page;
|
|||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
||||
import org.keycloak.admin.client.resource.RoleScopeResource;
|
||||
|
@ -71,6 +72,7 @@ import org.keycloak.testsuite.auth.page.login.SAMLIDPInitiatedLogin;
|
|||
import org.keycloak.testsuite.page.AbstractPage;
|
||||
import org.keycloak.testsuite.util.IOUtil;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
@ -104,6 +106,7 @@ import static org.junit.Assert.*;
|
|||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||
import static org.keycloak.testsuite.AbstractAuthTest.createUserRepresentation;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
|
||||
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.SAMLSERVLETDEMO;
|
||||
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
|
||||
import static org.keycloak.testsuite.util.IOUtil.loadXML;
|
||||
|
@ -529,6 +532,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
|||
testSuccessfulAndUnauthorizedLogin(salesMetadataServletPage, testRealmSAMLPostLoginPage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void salesPostTestCompositeRoleForUser() {
|
||||
UserRepresentation topGroupUser = createUserRepresentation("topGroupUser", "top@redhat.com", "", "", true);
|
||||
setPasswordFor(topGroupUser, PASSWORD);
|
||||
|
||||
assertSuccessfulLogin(salesPostServletPage, topGroupUser, testRealmSAMLPostLoginPage, "principal=topgroupuser");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void salesPostTest() {
|
||||
testSuccessfulAndUnauthorizedLogin(salesPostServletPage, testRealmSAMLPostLoginPage);
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin.authentication;
|
|||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
||||
|
@ -31,6 +32,10 @@ import javax.ws.rs.core.Response;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.keycloak.testsuite.util.Matchers.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
|
@ -195,7 +200,9 @@ public class FlowTest extends AbstractAuthenticationTest {
|
|||
// copy using existing alias as new name
|
||||
Response response = authMgmtResource.copy("browser", params);
|
||||
try {
|
||||
Assert.assertEquals("Copy flow using the new alias of existing flow should fail", 409, response.getStatus());
|
||||
Assert.assertThat("Copy flow using the new alias of existing flow should fail", response, statusCodeIs(Status.CONFLICT));
|
||||
Assert.assertThat("Copy flow using the new alias of existing flow should fail", response, body(containsString("already exists")));
|
||||
Assert.assertThat("Copy flow using the new alias of existing flow should fail", response, body(containsString("flow alias")));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
@ -204,7 +211,7 @@ public class FlowTest extends AbstractAuthenticationTest {
|
|||
params.clear();
|
||||
response = authMgmtResource.copy("non-existent", params);
|
||||
try {
|
||||
Assert.assertEquals("Copy non-existing flow", 404, response.getStatus());
|
||||
Assert.assertThat("Copy non-existing flow", response, statusCodeIs(Status.NOT_FOUND));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
@ -214,7 +221,7 @@ public class FlowTest extends AbstractAuthenticationTest {
|
|||
response = authMgmtResource.copy("browser", params);
|
||||
assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
|
||||
try {
|
||||
Assert.assertEquals("Copy flow", 201, response.getStatus());
|
||||
Assert.assertThat("Copy flow", response, statusCodeIs(Status.CREATED));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.keycloak.testsuite.util;
|
||||
|
||||
import org.keycloak.testsuite.util.matchers.ResponseBodyMatcher;
|
||||
import org.keycloak.testsuite.util.matchers.ResponseHeaderMatcher;
|
||||
import org.keycloak.testsuite.util.matchers.ResponseStatusCodeMatcher;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
/**
|
||||
* Additional hamcrest matchers for use in {@link org.junit.Assert#assertThat}.
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class Matchers {
|
||||
|
||||
/**
|
||||
* Matcher on HTTP status code of a {@link Response} instance.
|
||||
* @param matcher
|
||||
* @return
|
||||
*/
|
||||
public static Matcher<Response> body(Matcher<String> matcher) {
|
||||
return new ResponseBodyMatcher(matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matcher on HTTP status code of a {@link Response} instance.
|
||||
* @param matcher
|
||||
* @return
|
||||
*/
|
||||
public static Matcher<Response> statusCode(Matcher<? extends Number> matcher) {
|
||||
return new ResponseStatusCodeMatcher(matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches when the HTTP status code of a {@link Response} instance is equal to the given code.
|
||||
* @param expectedStatusCode
|
||||
* @return
|
||||
*/
|
||||
public static Matcher<Response> statusCodeIs(Response.Status expectedStatusCode) {
|
||||
return new ResponseStatusCodeMatcher(org.hamcrest.Matchers.is(expectedStatusCode.getStatusCode()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches when the HTTP status code of a {@link Response} instance is equal to the given code.
|
||||
* @param expectedStatusCode
|
||||
* @return
|
||||
*/
|
||||
public static Matcher<Response> statusCodeIs(int expectedStatusCode) {
|
||||
return new ResponseStatusCodeMatcher(org.hamcrest.Matchers.is(expectedStatusCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches when the HTTP status code of a {@link Response} instance is equal to the given code.
|
||||
* @param expectedStatusCode
|
||||
* @return
|
||||
*/
|
||||
public static <T> Matcher<Response> header(Matcher<Map<String, T>> matcher) {
|
||||
return new ResponseHeaderMatcher(matcher);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.keycloak.testsuite.util.matchers;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
/**
|
||||
* Matcher for matching status code of {@link Response} instance.
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class ResponseBodyMatcher extends BaseMatcher<Response> {
|
||||
|
||||
private final Matcher<String> matcher;
|
||||
|
||||
public ResponseBodyMatcher(Matcher<String> matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
if (item instanceof Response) {
|
||||
final Response rItem = (Response) item;
|
||||
rItem.bufferEntity();
|
||||
return this.matcher.matches(rItem.readEntity(String.class));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("response body matches ").appendDescriptionOf(this.matcher);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.keycloak.testsuite.util.matchers;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
/**
|
||||
* Matcher for matching status code of {@link Response} instance.
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class ResponseHeaderMatcher<T> extends BaseMatcher<Response> {
|
||||
|
||||
private final Matcher<Map<String, T>> matcher;
|
||||
|
||||
public ResponseHeaderMatcher(Matcher<Map<String, T>> matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
return (item instanceof Response) && this.matcher.matches(((Response) item).getHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("response headers match ").appendDescriptionOf(this.matcher);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.keycloak.testsuite.util.matchers;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
/**
|
||||
* Matcher for matching status code of {@link Response} instance.
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class ResponseStatusCodeMatcher extends BaseMatcher<Response> {
|
||||
|
||||
private final Matcher<? extends Number> matcher;
|
||||
|
||||
public ResponseStatusCodeMatcher(Matcher<? extends Number> matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
return (item instanceof Response) && this.matcher.matches(((Response) item).getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("response status code matches ").appendDescriptionOf(this.matcher);
|
||||
}
|
||||
|
||||
}
|
|
@ -49,6 +49,7 @@
|
|||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "realm-composite-role" ],
|
||||
"groups": [
|
||||
"/top"
|
||||
]
|
||||
|
@ -75,6 +76,14 @@
|
|||
{
|
||||
"name": "admin",
|
||||
"description": "Administrator privileges"
|
||||
},
|
||||
{
|
||||
"name": "realm-composite-role",
|
||||
"description": "Realm composite role containing user role",
|
||||
"composite": true,
|
||||
"composites": {
|
||||
"realm": ["user"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -66,18 +66,27 @@ public class CompositeRolesModelTest extends AbstractModelTest {
|
|||
@Test
|
||||
public void testComposites() {
|
||||
Set<RoleModel> requestedRoles = getRequestedRoles("APP_COMPOSITE_APPLICATION", "APP_COMPOSITE_USER");
|
||||
Assert.assertEquals(2, requestedRoles.size());
|
||||
Assert.assertEquals(5, requestedRoles.size());
|
||||
assertContains("APP_COMPOSITE_APPLICATION", "APP_COMPOSITE_ROLE", requestedRoles);
|
||||
assertContains("APP_COMPOSITE_APPLICATION", "APP_COMPOSITE_CHILD", requestedRoles);
|
||||
assertContains("APP_COMPOSITE_APPLICATION", "APP_ROLE_2", requestedRoles);
|
||||
assertContains("APP_ROLE_APPLICATION", "APP_ROLE_1", requestedRoles);
|
||||
assertContains("realm", "REALM_ROLE_1", requestedRoles);
|
||||
|
||||
requestedRoles = getRequestedRoles("APP_COMPOSITE_APPLICATION", "REALM_APP_COMPOSITE_USER");
|
||||
Assert.assertEquals(1, requestedRoles.size());
|
||||
Assert.assertEquals(4, requestedRoles.size());
|
||||
assertContains("APP_ROLE_APPLICATION", "APP_ROLE_1", requestedRoles);
|
||||
|
||||
requestedRoles = getRequestedRoles("REALM_COMPOSITE_1_APPLICATION", "REALM_COMPOSITE_1_USER");
|
||||
Assert.assertEquals(1, requestedRoles.size());
|
||||
assertContains("realm", "REALM_COMPOSITE_1", requestedRoles);
|
||||
|
||||
requestedRoles = getRequestedRoles("REALM_COMPOSITE_2_APPLICATION", "REALM_COMPOSITE_1_USER");
|
||||
Assert.assertEquals(3, requestedRoles.size());
|
||||
assertContains("realm", "REALM_COMPOSITE_1", requestedRoles);
|
||||
assertContains("realm", "REALM_COMPOSITE_CHILD", requestedRoles);
|
||||
assertContains("realm", "REALM_ROLE_4", requestedRoles);
|
||||
|
||||
requestedRoles = getRequestedRoles("REALM_ROLE_1_APPLICATION", "REALM_COMPOSITE_1_USER");
|
||||
Assert.assertEquals(1, requestedRoles.size());
|
||||
assertContains("realm", "REALM_ROLE_1", requestedRoles);
|
||||
|
|
|
@ -80,6 +80,10 @@
|
|||
"client": "REALM_COMPOSITE_1_APPLICATION",
|
||||
"roles": ["REALM_COMPOSITE_1"]
|
||||
},
|
||||
{
|
||||
"client": "REALM_COMPOSITE_2_APPLICATION",
|
||||
"roles": ["REALM_COMPOSITE_1", "REALM_COMPOSITE_CHILD", "REALM_ROLE_4"]
|
||||
},
|
||||
{
|
||||
"client": "REALM_ROLE_1_APPLICATION",
|
||||
"roles": ["REALM_ROLE_1"]
|
||||
|
@ -94,6 +98,14 @@
|
|||
"adminUrl": "http://localhost:8081/app/logout",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"name": "REALM_COMPOSITE_2_APPLICATION",
|
||||
"fullScopeAllowed": false,
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8081/app",
|
||||
"adminUrl": "http://localhost:8081/app/logout",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"name": "REALM_ROLE_1_APPLICATION",
|
||||
"fullScopeAllowed": false,
|
||||
|
@ -130,10 +142,19 @@
|
|||
{
|
||||
"name": "REALM_ROLE_3"
|
||||
},
|
||||
{
|
||||
"name": "REALM_ROLE_4"
|
||||
},
|
||||
{
|
||||
"name": "REALM_COMPOSITE_1",
|
||||
"composites": {
|
||||
"realm": ["REALM_ROLE_1"]
|
||||
"realm": ["REALM_ROLE_1", "REALM_COMPOSITE_CHILD"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "REALM_COMPOSITE_CHILD",
|
||||
"composites": {
|
||||
"realm": ["REALM_ROLE_4"]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -142,6 +163,9 @@
|
|||
"application": {
|
||||
"APP_ROLE_APPLICATION" :[
|
||||
"APP_ROLE_1"
|
||||
],
|
||||
"APP_COMPOSITE_APPLICATION" :[
|
||||
"APP_COMPOSITE_ROLE"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +192,19 @@
|
|||
"application": {
|
||||
"APP_ROLE_APPLICATION" :[
|
||||
"APP_ROLE_1"
|
||||
],
|
||||
"APP_COMPOSITE_APPLICATION" :[
|
||||
"APP_COMPOSITE_CHILD"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "APP_COMPOSITE_CHILD",
|
||||
"composites": {
|
||||
"application": {
|
||||
"APP_COMPOSITE_APPLICATION" :[
|
||||
"APP_ROLE_2"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +221,7 @@
|
|||
"APP_ROLE_APPLICATION": [
|
||||
{
|
||||
"client": "APP_COMPOSITE_APPLICATION",
|
||||
"roles": ["APP_ROLE_2"]
|
||||
"roles": ["APP_ROLE_1"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -868,6 +868,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
|
|||
$scope.samlForceNameIdFormat = false;
|
||||
$scope.disableAuthorizationTab = !client.authorizationServicesEnabled;
|
||||
$scope.disableServiceAccountRolesTab = !client.serviceAccountsEnabled;
|
||||
$scope.disableCredentialsTab = client.publicClient;
|
||||
|
||||
function updateProperties() {
|
||||
if (!$scope.client.attributes) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<ul class="nav nav-tabs" data-ng-hide="create && !path[4]">
|
||||
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'settings' | translate}}</a></li>
|
||||
<li ng-class="{active: path[4] == 'credentials'}" data-ng-show="!client.publicClient && client.protocol != 'saml'"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials">{{:: 'credentials' | translate}}</a></li>
|
||||
<li ng-class="{active: path[4] == 'credentials'}" data-ng-show="!disableCredentialsTab && !client.publicClient && client.protocol != 'saml'"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials">{{:: 'credentials' | translate}}</a></li>
|
||||
<li ng-class="{active: path[4] == 'saml'}" data-ng-show="client.protocol == 'saml' && (client.attributes['saml.client.signature'] == 'true' || client.attributes['saml.encrypt'] == 'true')"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/saml/keys">{{:: 'saml-keys' | translate}}</a></li>
|
||||
<li ng-class="{active: path[4] == 'roles'}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/roles">{{:: 'roles' | translate}}</a></li>
|
||||
<li ng-class="{active: path[4] == 'mappers'}" data-ng-show="!client.bearerOnly">
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
<supplement name="default">
|
||||
<replacement placeholder="CACHE-CONTAINERS">
|
||||
<cache-container name="keycloak" jndi-name="infinispan/Keycloak">
|
||||
<local-cache name="realms"/>
|
||||
<local-cache name="realms">
|
||||
<eviction max-entries="10000" strategy="LRU"/>
|
||||
</local-cache>
|
||||
<local-cache name="users">
|
||||
<eviction max-entries="10000" strategy="LRU"/>
|
||||
</local-cache>
|
||||
|
@ -92,7 +94,9 @@
|
|||
<replacement placeholder="CACHE-CONTAINERS">
|
||||
<cache-container name="keycloak" jndi-name="infinispan/Keycloak">
|
||||
<transport lock-timeout="60000"/>
|
||||
<local-cache name="realms"/>
|
||||
<local-cache name="realms">
|
||||
<eviction max-entries="10000" strategy="LRU"/>
|
||||
</local-cache>
|
||||
<local-cache name="users">
|
||||
<eviction max-entries="10000" strategy="LRU"/>
|
||||
</local-cache>
|
||||
|
|
Loading…
Reference in a new issue