Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
b4f56e92f3
64 changed files with 6265 additions and 2975 deletions
|
@ -35,7 +35,7 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Rule";
|
||||
return "Rules";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,7 +75,7 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory {
|
|||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "drools";
|
||||
return "rules";
|
||||
}
|
||||
|
||||
void update(Policy policy) {
|
||||
|
|
|
@ -29,39 +29,10 @@
|
|||
<fileSet>
|
||||
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/modules/system/layers/keycloak</directory>
|
||||
<outputDirectory>modules/system/add-ons/keycloak</outputDirectory>
|
||||
<includes>
|
||||
<include>com/google/zxing/**</include>
|
||||
<include>org/freemarker/**</include>
|
||||
<include>org/jboss/aesh/0.65/**</include>
|
||||
<include>org/keycloak/**</include>
|
||||
<include>org/liquibase/**</include>
|
||||
<include>org/mongodb/**</include>
|
||||
<include>org/twitter4j/**</include>
|
||||
<include>aopalliance/**</include>
|
||||
<include>com/thoughtworks/xstream/**</include>
|
||||
<include>org/antlr/**</include>
|
||||
<include>org/apache/**</include>
|
||||
<include>org/codehouse/**</include>
|
||||
<include>org/drools/**</include>
|
||||
<include>org/eclipse/**</include>
|
||||
<include>org/kie/**</include>
|
||||
<include>org/mvel/**</include>
|
||||
<include>org/sonatype/**</include>
|
||||
<include>sun/jdk/jgss/**</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<!-- Authorization -->
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/modules/system/layers/keycloak-authz</directory>
|
||||
<outputDirectory>modules/system/add-ons/keycloak-authz</outputDirectory>
|
||||
<includes>
|
||||
<include>**/**</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/content</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/themes</directory>
|
||||
<outputDirectory>themes</outputDirectory>
|
||||
|
@ -90,6 +61,23 @@
|
|||
</includes>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/bin</directory>
|
||||
<includes>
|
||||
<include>add-user-keycloak.*</include>
|
||||
<include>federation-sssd-setup.sh</include>
|
||||
<include>kcadm.*</include>
|
||||
<include>kcreg.*</include>
|
||||
</includes>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/bin/client</directory>
|
||||
<includes>
|
||||
<include>keycloak*</include>
|
||||
</includes>
|
||||
<outputDirectory>bin/client</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
|
||||
<files>
|
||||
|
@ -97,16 +85,6 @@
|
|||
<source>target/README.txt</source>
|
||||
<outputDirectory></outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.build.directory}/unpacked/keycloak-${project.version}/bin/add-user-keycloak.sh</source>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<destName>add-user-keycloak.sh</destName>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.build.directory}/unpacked/keycloak-${project.version}/bin/add-user-keycloak.bat</source>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<destName>add-user-keycloak.bat</destName>
|
||||
</file>
|
||||
</files>
|
||||
|
||||
</assembly>
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
{
|
||||
"name": "Only Owner Policy",
|
||||
"description": "Defines that only the resource owner is allowed to do something",
|
||||
"type": "drools",
|
||||
"type": "rules",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserManager;
|
||||
import org.keycloak.models.cache.UserCache;
|
||||
import org.keycloak.models.credential.PasswordUserCredentialModel;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
|
@ -49,9 +50,10 @@ import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
|||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||
import org.keycloak.storage.ldap.kerberos.LDAPProviderKerberosConfig;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPMappersComparator;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
|
||||
import org.keycloak.storage.ldap.mappers.PasswordUpdated;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPStorageMapperManager;
|
||||
import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
|
||||
import org.keycloak.storage.user.ImportedUserValidation;
|
||||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
import org.keycloak.storage.user.UserQueryProvider;
|
||||
|
@ -59,7 +61,6 @@ import org.keycloak.storage.user.UserRegistrationProvider;
|
|||
|
||||
import javax.naming.AuthenticationException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -89,7 +90,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
protected LDAPIdentityStore ldapIdentityStore;
|
||||
protected EditMode editMode;
|
||||
protected LDAPProviderKerberosConfig kerberosConfig;
|
||||
protected PasswordUpdated updater;
|
||||
protected PasswordUpdateCallback updater;
|
||||
protected LDAPStorageMapperManager mapperManager;
|
||||
|
||||
protected final Set<String> supportedCredentialTypes = new HashSet<>();
|
||||
|
||||
|
@ -100,6 +102,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
this.ldapIdentityStore = ldapIdentityStore;
|
||||
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
|
||||
this.editMode = ldapIdentityStore.getConfig().getEditMode();
|
||||
this.mapperManager = new LDAPStorageMapperManager(this);
|
||||
|
||||
supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
|
||||
if (kerberosConfig.isAllowKerberosAuthentication()) {
|
||||
|
@ -107,7 +110,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
}
|
||||
}
|
||||
|
||||
public void setUpdater(PasswordUpdated updater) {
|
||||
public void setUpdater(PasswordUpdateCallback updater) {
|
||||
this.updater = updater;
|
||||
}
|
||||
|
||||
|
@ -127,6 +130,10 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
return model;
|
||||
}
|
||||
|
||||
public LDAPStorageMapperManager getMapperManager() {
|
||||
return mapperManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel validate(RealmModel realm, UserModel local) {
|
||||
LDAPObject ldapObject = loadAndValidateUser(realm, local);
|
||||
|
@ -154,9 +161,9 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
|
||||
List<ComponentModel> sortedMappers = sortMappersAsc(mappers);
|
||||
List<ComponentModel> sortedMappers = mapperManager.sortMappersAsc(mappers);
|
||||
for (ComponentModel mapperModel : sortedMappers) {
|
||||
LDAPStorageMapper ldapMapper = getMapper(mapperModel);
|
||||
LDAPStorageMapper ldapMapper = mapperManager.getMapper(mapperModel);
|
||||
proxied = ldapMapper.proxy(ldapObject, proxied, realm);
|
||||
}
|
||||
|
||||
|
@ -299,9 +306,9 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
|
||||
List<ComponentModel> sortedMappers = sortMappersAsc(mappers);
|
||||
List<ComponentModel> sortedMappers = mapperManager.sortMappersAsc(mappers);
|
||||
for (ComponentModel mapperModel : sortedMappers) {
|
||||
LDAPStorageMapper ldapMapper = getMapper(mapperModel);
|
||||
LDAPStorageMapper ldapMapper = mapperManager.getMapper(mapperModel);
|
||||
List<UserModel> users = ldapMapper.getGroupMembers(realm, group, firstResult, maxResults);
|
||||
|
||||
// Sufficient for now
|
||||
|
@ -410,12 +417,12 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
imported.setEnabled(true);
|
||||
|
||||
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
|
||||
List<ComponentModel> sortedMappers = sortMappersDesc(mappers);
|
||||
List<ComponentModel> sortedMappers = mapperManager.sortMappersDesc(mappers);
|
||||
for (ComponentModel mapperModel : sortedMappers) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Using mapper %s during import user from LDAP", mapperModel);
|
||||
}
|
||||
LDAPStorageMapper ldapMapper = getMapper(mapperModel);
|
||||
LDAPStorageMapper ldapMapper = mapperManager.getMapper(mapperModel);
|
||||
ldapMapper.onImportUserFromLDAP(ldapUser, imported, realm, true);
|
||||
}
|
||||
|
||||
|
@ -492,12 +499,12 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
} catch (AuthenticationException ae) {
|
||||
boolean processed = false;
|
||||
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
|
||||
List<ComponentModel> sortedMappers = sortMappersDesc(mappers);
|
||||
List<ComponentModel> sortedMappers = mapperManager.sortMappersDesc(mappers);
|
||||
for (ComponentModel mapperModel : sortedMappers) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Using mapper %s during import user from LDAP", mapperModel);
|
||||
}
|
||||
LDAPStorageMapper ldapMapper = getMapper(mapperModel);
|
||||
LDAPStorageMapper ldapMapper = mapperManager.getMapper(mapperModel);
|
||||
processed = processed || ldapMapper.onAuthenticationFailure(ldapUser, user, ae, realm);
|
||||
}
|
||||
return processed;
|
||||
|
@ -508,23 +515,29 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!CredentialModel.PASSWORD.equals(input.getType()) || ! (input instanceof UserCredentialModel)) return false;
|
||||
if (!CredentialModel.PASSWORD.equals(input.getType()) || ! (input instanceof PasswordUserCredentialModel)) return false;
|
||||
if (editMode == UserStorageProvider.EditMode.READ_ONLY) {
|
||||
throw new ModelReadOnlyException("Federated storage is not writable");
|
||||
|
||||
} else if (editMode == UserStorageProvider.EditMode.WRITABLE) {
|
||||
LDAPIdentityStore ldapIdentityStore = getLdapIdentityStore();
|
||||
UserCredentialModel cred = (UserCredentialModel)input;
|
||||
PasswordUserCredentialModel cred = (PasswordUserCredentialModel)input;
|
||||
String password = cred.getValue();
|
||||
LDAPObject ldapUser = loadAndValidateUser(realm, user);
|
||||
|
||||
try {
|
||||
ldapIdentityStore.updatePassword(ldapUser, password);
|
||||
if (updater != null) updater.passwordUpdated(user, ldapUser, input);
|
||||
LDAPOperationDecorator operationDecorator = null;
|
||||
if (updater != null) {
|
||||
operationDecorator = updater.beforePasswordUpdate(user, ldapUser, cred);
|
||||
}
|
||||
|
||||
ldapIdentityStore.updatePassword(ldapUser, password, operationDecorator);
|
||||
|
||||
if (updater != null) updater.passwordUpdated(user, ldapUser, cred);
|
||||
return true;
|
||||
} catch (ModelException me) {
|
||||
if (updater != null) {
|
||||
updater.passwordUpdateFailed(user, ldapUser, input, me);
|
||||
updater.passwordUpdateFailed(user, ldapUser, cred, me);
|
||||
return false;
|
||||
} else {
|
||||
throw me;
|
||||
|
@ -667,23 +680,5 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
return ldapUser;
|
||||
}
|
||||
|
||||
public LDAPStorageMapper getMapper(ComponentModel mapperModel) {
|
||||
LDAPStorageMapper ldapMapper = getSession().getProvider(LDAPStorageMapper.class, mapperModel);
|
||||
if (ldapMapper == null) {
|
||||
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getProviderId());
|
||||
}
|
||||
|
||||
return ldapMapper;
|
||||
}
|
||||
|
||||
|
||||
public List<ComponentModel> sortMappersAsc(Collection<ComponentModel> mappers) {
|
||||
return LDAPMappersComparator.sortAsc(getLdapIdentityStore().getConfig(), mappers);
|
||||
}
|
||||
|
||||
protected List<ComponentModel> sortMappersDesc(Collection<ComponentModel> mappers) {
|
||||
return LDAPMappersComparator.sortDesc(getLdapIdentityStore().getConfig(), mappers);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -521,9 +521,9 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
|
|||
|
||||
// Update keycloak user
|
||||
List<ComponentModel> federationMappers = currentRealm.getComponents(fedModel.getId(), LDAPStorageMapper.class.getName());
|
||||
List<ComponentModel> sortedMappers = ldapFedProvider.sortMappersDesc(federationMappers);
|
||||
List<ComponentModel> sortedMappers = ldapFedProvider.getMapperManager().sortMappersDesc(federationMappers);
|
||||
for (ComponentModel mapperModel : sortedMappers) {
|
||||
LDAPStorageMapper ldapMapper = ldapFedProvider.getMapper(mapperModel);
|
||||
LDAPStorageMapper ldapMapper = ldapFedProvider.getMapperManager().getMapper(mapperModel);
|
||||
ldapMapper.onImportUserFromLDAP(ldapUser, currentUser, currentRealm, false);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,9 +61,9 @@ public class LDAPUtils {
|
|||
ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses());
|
||||
|
||||
List<ComponentModel> federationMappers = realm.getComponents(ldapProvider.getModel().getId(), LDAPStorageMapper.class.getName());
|
||||
List<ComponentModel> sortedMappers = ldapProvider.sortMappersAsc(federationMappers);
|
||||
List<ComponentModel> sortedMappers = ldapProvider.getMapperManager().sortMappersAsc(federationMappers);
|
||||
for (ComponentModel mapperModel : sortedMappers) {
|
||||
LDAPStorageMapper ldapMapper = ldapProvider.getMapper(mapperModel);
|
||||
LDAPStorageMapper ldapMapper = ldapProvider.getMapperManager().getMapper(mapperModel);
|
||||
ldapMapper.onRegisterUserToLDAP(ldapUser, user, realm);
|
||||
}
|
||||
|
||||
|
|
|
@ -152,9 +152,9 @@ public class LDAPQuery {
|
|||
public List<LDAPObject> getResultList() {
|
||||
|
||||
// Apply mappers now
|
||||
List<ComponentModel> sortedMappers = ldapFedProvider.sortMappersAsc(mappers);
|
||||
List<ComponentModel> sortedMappers = ldapFedProvider.getMapperManager().sortMappersAsc(mappers);
|
||||
for (ComponentModel mapperModel : sortedMappers) {
|
||||
LDAPStorageMapper fedMapper = ldapFedProvider.getMapper(mapperModel);
|
||||
LDAPStorageMapper fedMapper = ldapFedProvider.getMapperManager().getMapper(mapperModel);
|
||||
fedMapper.beforeLDAPQuery(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.storage.ldap.idm.store;
|
|||
import org.keycloak.storage.ldap.LDAPConfig;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||
|
||||
import javax.naming.AuthenticationException;
|
||||
import java.util.List;
|
||||
|
@ -92,7 +93,8 @@ public interface IdentityStore {
|
|||
*
|
||||
* @param user Keycloak user
|
||||
* @param password Ldap password
|
||||
* @param passwordUpdateDecorator Callback to be executed before/after password update. Can be null
|
||||
*/
|
||||
void updatePassword(LDAPObject user, String password);
|
||||
void updatePassword(LDAPObject user, String password, LDAPOperationDecorator passwordUpdateDecorator);
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
|
|||
import org.keycloak.storage.ldap.idm.query.internal.EqualCondition;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.storage.ldap.idm.store.IdentityStore;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||
|
||||
import javax.naming.AuthenticationException;
|
||||
import javax.naming.NamingEnumeration;
|
||||
|
@ -205,7 +206,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void updatePassword(LDAPObject user, String password) {
|
||||
public void updatePassword(LDAPObject user, String password, LDAPOperationDecorator passwordUpdateDecorator) {
|
||||
String userDN = user.getDn().toString();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
@ -213,7 +214,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
}
|
||||
|
||||
if (getConfig().isActiveDirectory()) {
|
||||
updateADPassword(userDN, password);
|
||||
updateADPassword(userDN, password, passwordUpdateDecorator);
|
||||
} else {
|
||||
ModificationItem[] mods = new ModificationItem[1];
|
||||
|
||||
|
@ -222,7 +223,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
|
||||
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
|
||||
|
||||
operationManager.modifyAttribute(userDN, mod0);
|
||||
operationManager.modifyAttributes(userDN, mods, passwordUpdateDecorator);
|
||||
} catch (ModelException me) {
|
||||
throw me;
|
||||
} catch (Exception e) {
|
||||
|
@ -232,7 +233,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
}
|
||||
|
||||
|
||||
private void updateADPassword(String userDN, String password) {
|
||||
private void updateADPassword(String userDN, String password, LDAPOperationDecorator passwordUpdateDecorator) {
|
||||
try {
|
||||
// Replace the "unicdodePwd" attribute with a new value
|
||||
// Password must be both Unicode and a quoted string
|
||||
|
@ -244,7 +245,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
List<ModificationItem> modItems = new ArrayList<ModificationItem>();
|
||||
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, unicodePwd));
|
||||
|
||||
operationManager.modifyAttributes(userDN, modItems.toArray(new ModificationItem[] {}));
|
||||
operationManager.modifyAttributes(userDN, modItems.toArray(new ModificationItem[] {}), passwordUpdateDecorator);
|
||||
} catch (ModelException me) {
|
||||
throw me;
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.models.LDAPConstants;
|
|||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.storage.ldap.LDAPConfig;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||
|
||||
import javax.naming.AuthenticationException;
|
||||
import javax.naming.Binding;
|
||||
|
@ -81,7 +82,7 @@ public class LDAPOperationManager {
|
|||
*/
|
||||
public void modifyAttribute(String dn, Attribute attribute) {
|
||||
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute)};
|
||||
modifyAttributes(dn, mods);
|
||||
modifyAttributes(dn, mods, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +102,7 @@ public class LDAPOperationManager {
|
|||
modItems.add(modItem);
|
||||
}
|
||||
|
||||
modifyAttributes(dn, modItems.toArray(new ModificationItem[] {}));
|
||||
modifyAttributes(dn, modItems.toArray(new ModificationItem[] {}), null);
|
||||
} catch (NamingException ne) {
|
||||
throw new ModelException("Could not modify attributes on entry from DN [" + dn + "]", ne);
|
||||
}
|
||||
|
@ -119,7 +120,7 @@ public class LDAPOperationManager {
|
|||
*/
|
||||
public void removeAttribute(String dn, Attribute attribute) {
|
||||
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attribute)};
|
||||
modifyAttributes(dn, mods);
|
||||
modifyAttributes(dn, mods, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,7 +133,7 @@ public class LDAPOperationManager {
|
|||
*/
|
||||
public void addAttribute(String dn, Attribute attribute) {
|
||||
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.ADD_ATTRIBUTE, attribute)};
|
||||
modifyAttributes(dn, mods);
|
||||
modifyAttributes(dn, mods, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -379,7 +380,7 @@ public class LDAPOperationManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void modifyAttributes(final String dn, final ModificationItem[] mods) {
|
||||
public void modifyAttributes(final String dn, final ModificationItem[] mods, LDAPOperationDecorator decorator) {
|
||||
try {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Modifying attributes for entry [%s]: [", dn);
|
||||
|
@ -405,7 +406,7 @@ public class LDAPOperationManager {
|
|||
context.modifyAttributes(dn, mods);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, decorator);
|
||||
} catch (NamingException e) {
|
||||
throw new ModelException("Could not modify attribute for DN [" + dn + "]", e);
|
||||
}
|
||||
|
@ -546,13 +547,19 @@ public class LDAPOperationManager {
|
|||
}
|
||||
|
||||
private <R> R execute(LdapOperation<R> operation) throws NamingException {
|
||||
return execute(operation, null);
|
||||
}
|
||||
|
||||
private <R> R execute(LdapOperation<R> operation, LDAPOperationDecorator decorator) throws NamingException {
|
||||
LdapContext context = null;
|
||||
|
||||
try {
|
||||
context = createLdapContext();
|
||||
if (decorator != null) {
|
||||
decorator.beforeLDAPOperation(context, operation);
|
||||
}
|
||||
|
||||
return operation.execute(context);
|
||||
} catch (NamingException ne) {
|
||||
throw ne;
|
||||
} finally {
|
||||
if (context != null) {
|
||||
try {
|
||||
|
@ -564,7 +571,7 @@ public class LDAPOperationManager {
|
|||
}
|
||||
}
|
||||
|
||||
private interface LdapOperation<R> {
|
||||
public interface LdapOperation<R> {
|
||||
R execute(LdapContext context) throws NamingException;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,11 @@ public class FullNameLDAPStorageMapperFactory extends AbstractLDAPStorageMapperF
|
|||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
|
||||
return getConfigProps(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
|
|
|
@ -15,21 +15,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.freedesktop.sssd.infopipe;
|
||||
package org.keycloak.storage.ldap.mappers;
|
||||
|
||||
import org.freedesktop.dbus.DBusInterface;
|
||||
import org.freedesktop.dbus.DBusInterfaceName;
|
||||
import org.freedesktop.dbus.DBusMemberName;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.ldap.LdapContext;
|
||||
|
||||
import org.keycloak.storage.ldap.idm.store.ldap.LDAPOperationManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@DBusInterfaceName("org.freedesktop.sssd.infopipe.Users")
|
||||
public interface User extends DBusInterface {
|
||||
public interface LDAPOperationDecorator {
|
||||
|
||||
String OBJECTPATH = "/org/freedesktop/sssd/infopipe/Users";
|
||||
|
||||
@DBusMemberName("FindByCertificate")
|
||||
DBusInterface findByCertificate(String pem_cert);
|
||||
void beforeLDAPOperation(LdapContext ldapContext, LDAPOperationManager.LdapOperation ldapOperation) throws NamingException;
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.storage.ldap.mappers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
|
||||
/**
|
||||
* TODO: LDAPStorageMapper should be divided into more interfaces and let the LDAPStorageMapperManager to check which operation (feature) is supported by which mapper implementation
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class LDAPStorageMapperManager {
|
||||
|
||||
private final LDAPStorageProvider ldapProvider;
|
||||
|
||||
public LDAPStorageMapperManager(LDAPStorageProvider ldapProvider) {
|
||||
this.ldapProvider = ldapProvider;
|
||||
}
|
||||
|
||||
public LDAPStorageMapper getMapper(ComponentModel mapperModel) {
|
||||
LDAPStorageMapper ldapMapper = ldapProvider.getSession().getProvider(LDAPStorageMapper.class, mapperModel);
|
||||
if (ldapMapper == null) {
|
||||
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getProviderId());
|
||||
}
|
||||
|
||||
return ldapMapper;
|
||||
}
|
||||
|
||||
|
||||
public List<ComponentModel> sortMappersAsc(Collection<ComponentModel> mappers) {
|
||||
return LDAPMappersComparator.sortAsc(ldapProvider.getLdapIdentityStore().getConfig(), mappers);
|
||||
}
|
||||
|
||||
public List<ComponentModel> sortMappersDesc(Collection<ComponentModel> mappers) {
|
||||
return LDAPMappersComparator.sortDesc(ldapProvider.getLdapIdentityStore().getConfig(), mappers);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -16,18 +16,20 @@
|
|||
*/
|
||||
package org.keycloak.storage.ldap.mappers;
|
||||
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.PasswordUserCredentialModel;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface PasswordUpdated {
|
||||
public interface PasswordUpdateCallback {
|
||||
|
||||
void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input);
|
||||
LDAPOperationDecorator beforePasswordUpdate(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password);
|
||||
|
||||
void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, CredentialInput input, ModelException exception) throws ModelException;
|
||||
void passwordUpdated(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password);
|
||||
|
||||
void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password, ModelException exception) throws ModelException;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.storage.ldap.mappers.msad;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.ldap.BasicControl;
|
||||
import javax.naming.ldap.LdapContext;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.storage.ldap.idm.store.ldap.LDAPOperationManager;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class LDAPServerPolicyHintsDecorator implements LDAPOperationDecorator {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(LDAPServerPolicyHintsDecorator.class);
|
||||
|
||||
public static final String LDAP_SERVER_POLICY_HINTS_OID = "1.2.840.113556.1.4.2239";
|
||||
public static final String LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID = "1.2.840.113556.1.4.2066";
|
||||
|
||||
@Override
|
||||
public void beforeLDAPOperation(LdapContext ldapContext, LDAPOperationManager.LdapOperation ldapOperation) throws NamingException {
|
||||
logger.debug("Applying LDAP_PASSWORD_POLICY_HINTS_OID before update password");
|
||||
|
||||
final byte[] controlData = {48, (byte) 132, 0, 0, 0, 3, 2, 1, 1};
|
||||
|
||||
// Rather using deprecated OID as it works from MSAD 2008-R2 when the newer works from MSAD 2012
|
||||
BasicControl control = new BasicControl(LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID, true, controlData);
|
||||
BasicControl[] controls = new BasicControl[] { control };
|
||||
ldapContext.setRequestControls(controls);
|
||||
}
|
||||
}
|
|
@ -24,13 +24,15 @@ import org.keycloak.models.LDAPConstants;
|
|||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.PasswordUserCredentialModel;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper;
|
||||
import org.keycloak.storage.ldap.mappers.PasswordUpdated;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||
import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
|
||||
|
||||
import javax.naming.AuthenticationException;
|
||||
import java.util.HashSet;
|
||||
|
@ -44,12 +46,14 @@ import java.util.regex.Pattern;
|
|||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdated {
|
||||
public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdateCallback {
|
||||
|
||||
public static final String LDAP_PASSWORD_POLICY_HINTS_ENABLED = "ldap.password.policy.hints.enabled";
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MSADUserAccountControlStorageMapper.class);
|
||||
|
||||
private static final Pattern AUTH_EXCEPTION_REGEX = Pattern.compile(".*AcceptSecurityContext error, data ([0-9a-f]*), v.*");
|
||||
private static final Pattern AUTH_INVALID_NEW_PASSWORD = Pattern.compile(".*error code ([0-9a-f]+) .*WILL_NOT_PERFORM.*");
|
||||
private static final Pattern AUTH_INVALID_NEW_PASSWORD = Pattern.compile(".*ERROR CODE ([0-9A-F]+) - ([0-9A-F]+): .*WILL_NOT_PERFORM.*");
|
||||
|
||||
public MSADUserAccountControlStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider) {
|
||||
super(mapperModel, ldapProvider);
|
||||
|
@ -70,7 +74,18 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
|
|||
}
|
||||
|
||||
@Override
|
||||
public void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input) {
|
||||
public LDAPOperationDecorator beforePasswordUpdate(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password) {
|
||||
// Not apply policies if password is reset by admin (not by user himself)
|
||||
if (password.isAdminRequest()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean applyDecorator = mapperModel.get(LDAP_PASSWORD_POLICY_HINTS_ENABLED, false);
|
||||
return applyDecorator ? new LDAPServerPolicyHintsDecorator() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passwordUpdated(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password) {
|
||||
logger.debugf("Going to update userAccountControl for ldap user '%s' after successful password update", ldapUser.getDn().toString());
|
||||
|
||||
// Normally it's read-only
|
||||
|
@ -90,7 +105,7 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
|
|||
}
|
||||
|
||||
@Override
|
||||
public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, CredentialInput input, ModelException exception) {
|
||||
public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password, ModelException exception) {
|
||||
throw processFailedPasswordUpdateException(exception);
|
||||
}
|
||||
|
||||
|
@ -148,12 +163,17 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
|
|||
}
|
||||
|
||||
String exceptionMessage = e.getCause().getMessage().replace('\n', ' ');
|
||||
logger.debugf("Failed to update password in Active Directory. Exception message: %s", exceptionMessage);
|
||||
exceptionMessage = exceptionMessage.toUpperCase();
|
||||
|
||||
Matcher m = AUTH_INVALID_NEW_PASSWORD.matcher(exceptionMessage);
|
||||
if (m.matches()) {
|
||||
String errorCode = m.group(1);
|
||||
if (errorCode.equals("53")) {
|
||||
ModelException me = new ModelException("invalidPasswordRegexPatternMessage", e);
|
||||
me.setParameters(new Object[]{"passwordConstraintViolation"});
|
||||
String errorCode2 = m.group(2);
|
||||
|
||||
// 52D corresponds to ERROR_PASSWORD_RESTRICTION. See https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
|
||||
if ((errorCode.equals("53")) && errorCode2.endsWith("52D")) {
|
||||
ModelException me = new ModelException("invalidPasswordGenericMessage", e);
|
||||
return me;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,13 @@ import org.keycloak.component.ComponentModel;
|
|||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPConfig;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper;
|
||||
import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapperFactory;
|
||||
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -34,9 +38,23 @@ import java.util.List;
|
|||
public class MSADUserAccountControlStorageMapperFactory extends AbstractLDAPStorageMapperFactory {
|
||||
|
||||
public static final String PROVIDER_ID = LDAPConstants.MSAD_USER_ACCOUNT_CONTROL_MAPPER;
|
||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
protected static final List<ProviderConfigProperty> configProperties;
|
||||
|
||||
static {
|
||||
configProperties = getConfigProps(null);
|
||||
}
|
||||
|
||||
private static List<ProviderConfigProperty> getConfigProps(ComponentModel parent) {
|
||||
return ProviderConfigurationBuilder.create()
|
||||
.property().name(MSADUserAccountControlStorageMapper.LDAP_PASSWORD_POLICY_HINTS_ENABLED)
|
||||
.label("Password Policy Hints Enabled")
|
||||
.helpText("Applicable just for writable MSAD. If on, then updating password in MSAD will use LDAP_SERVER_POLICY_HINTS_OID " +
|
||||
"extension, which means that advanced MSAD password policies like 'password history' or 'minimal password age' will be applied. This extension works just for MSAD 2008 R2 or newer.")
|
||||
.type(ProviderConfigProperty.BOOLEAN_TYPE)
|
||||
.defaultValue("false")
|
||||
.add()
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -50,6 +68,11 @@ public class MSADUserAccountControlStorageMapperFactory extends AbstractLDAPStor
|
|||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
|
||||
return getConfigProps(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
|
|
|
@ -24,13 +24,15 @@ import org.keycloak.models.LDAPConstants;
|
|||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.PasswordUserCredentialModel;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper;
|
||||
import org.keycloak.storage.ldap.mappers.PasswordUpdated;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
|
||||
import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
|
||||
|
||||
import javax.naming.AuthenticationException;
|
||||
import java.util.HashSet;
|
||||
|
@ -45,7 +47,7 @@ import java.util.regex.Pattern;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
* @author <a href="mailto:slawomir@dabek.name">Slawomir Dabek</a>
|
||||
*/
|
||||
public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdated {
|
||||
public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdateCallback {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MSADLDSUserAccountControlStorageMapper.class);
|
||||
|
||||
|
@ -71,7 +73,12 @@ public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageM
|
|||
}
|
||||
|
||||
@Override
|
||||
public void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input) {
|
||||
public LDAPOperationDecorator beforePasswordUpdate(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password) {
|
||||
return null; // Not supported for now. Not sure if LDAP_SERVER_POLICY_HINTS_OID works in MSAD LDS
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passwordUpdated(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password) {
|
||||
logger.debugf("Going to update pwdLastSet for ldap user '%s' after successful password update", ldapUser.getDn().toString());
|
||||
|
||||
// Normally it's read-only
|
||||
|
@ -89,7 +96,7 @@ public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageM
|
|||
}
|
||||
|
||||
@Override
|
||||
public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, CredentialInput input, ModelException exception) {
|
||||
public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password, ModelException exception) {
|
||||
throw processFailedPasswordUpdateException(exception);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
|
||||
package org.keycloak.federation.sssd;
|
||||
|
||||
import org.freedesktop.dbus.Variant;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputUpdater;
|
||||
import org.keycloak.credential.CredentialInputValidator;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.federation.sssd.api.Sssd;
|
||||
import org.keycloak.federation.sssd.api.Sssd.User;
|
||||
import org.keycloak.federation.sssd.impl.PAMAuthenticator;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
@ -34,7 +34,6 @@ import org.keycloak.storage.user.UserLookupProvider;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -112,14 +111,14 @@ public class SSSDFederationProvider implements UserStorageProvider,
|
|||
|
||||
protected UserModel importUserToKeycloak(RealmModel realm, String username) {
|
||||
Sssd sssd = new Sssd(username);
|
||||
Map<String, Variant> sssdUser = sssd.getUserAttributes();
|
||||
User sssdUser = sssd.getUser();
|
||||
logger.debugf("Creating SSSD user: %s to local Keycloak storage", username);
|
||||
UserModel user = session.userLocalStorage().addUser(realm, username);
|
||||
user.setEnabled(true);
|
||||
user.setEmail(Sssd.getRawAttribute(sssdUser.get("mail")));
|
||||
user.setFirstName(Sssd.getRawAttribute(sssdUser.get("givenname")));
|
||||
user.setLastName(Sssd.getRawAttribute(sssdUser.get("sn")));
|
||||
for (String s : sssd.getUserGroups()) {
|
||||
user.setEmail(sssdUser.getEmail());
|
||||
user.setFirstName(sssdUser.getFirstName());
|
||||
user.setLastName(sssdUser.getLastName());
|
||||
for (String s : sssd.getGroups()) {
|
||||
GroupModel group = KeycloakModelUtils.findGroupByPath(realm, "/" + s);
|
||||
if (group == null) {
|
||||
group = session.realms().createGroup(realm, s);
|
||||
|
@ -158,8 +157,8 @@ public class SSSDFederationProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
public boolean isValid(RealmModel realm, UserModel local) {
|
||||
Map<String, Variant> attributes = new Sssd(local.getUsername()).getUserAttributes();
|
||||
return Sssd.getRawAttribute(attributes.get("mail")).equalsIgnoreCase(local.getEmail());
|
||||
User user = new Sssd(local.getUsername()).getUser();
|
||||
return user.equals(local);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.freedesktop.dbus.Variant;
|
|||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.freedesktop.sssd.infopipe.InfoPipe;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -68,20 +69,7 @@ public class Sssd {
|
|||
return null;
|
||||
}
|
||||
|
||||
public Map<String, Variant> getUserAttributes() {
|
||||
String[] attr = {"mail", "givenname", "sn", "telephoneNumber"};
|
||||
Map<String, Variant> attributes = null;
|
||||
try {
|
||||
InfoPipe infoPipe = dBusConnection.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class);
|
||||
attributes = infoPipe.getUserAttributes(username, Arrays.asList(attr));
|
||||
} catch (Exception e) {
|
||||
throw new SSSDException("Failed to retrieve user's attributes. Check if SSSD service is active.");
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public List<String> getUserGroups() {
|
||||
public List<String> getGroups() {
|
||||
List<String> userGroups;
|
||||
try {
|
||||
InfoPipe infoPipe = dBusConnection.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class);
|
||||
|
@ -113,4 +101,70 @@ public class Sssd {
|
|||
return sssdAvailable;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
|
||||
String[] attr = {"mail", "givenname", "sn", "telephoneNumber"};
|
||||
User user = null;
|
||||
try {
|
||||
InfoPipe infoPipe = dBusConnection.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class);
|
||||
user = new User(infoPipe.getUserAttributes(username, Arrays.asList(attr)));
|
||||
} catch (Exception e) {
|
||||
throw new SSSDException("Failed to retrieve user's attributes. Check if SSSD service is active.");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public class User {
|
||||
|
||||
private final String email;
|
||||
private final String firstName;
|
||||
private final String lastName;
|
||||
|
||||
public User(Map<String, Variant> userAttributes) {
|
||||
this.email = getRawAttribute(userAttributes.get("mail"));
|
||||
this.firstName = getRawAttribute(userAttributes.get("givenname"));
|
||||
this.lastName = getRawAttribute(userAttributes.get("sn"));
|
||||
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
|
||||
UserModel userModel = (UserModel) o;
|
||||
if (firstName != null && !firstName.equals(userModel.getFirstName())) {
|
||||
return false;
|
||||
}
|
||||
if (lastName != null && !lastName.equals(userModel.getLastName())) {
|
||||
return false;
|
||||
}
|
||||
if (email != null) {
|
||||
return email.equals(userModel.getEmail());
|
||||
}
|
||||
if (email != userModel.getEmail()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = email != null ? email.hashCode() : 0;
|
||||
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
|
||||
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
29
model/jpa/src/main/resources/META-INF/jpa-changelog-authz-2.5.1.xml
Executable file
29
model/jpa/src/main/resources/META-INF/jpa-changelog-authz-2.5.1.xml
Executable file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--
|
||||
~ * 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.
|
||||
-->
|
||||
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
|
||||
<changeSet author="psilva@redhat.com" id="authz-2.5.1">
|
||||
<update tableName="RESOURCE_SERVER_POLICY">
|
||||
<column name="TYPE" value="rules"/>
|
||||
<where>TYPE = :value</where>
|
||||
<whereParams>
|
||||
<param value="drools" />
|
||||
</whereParams>
|
||||
</update>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -19,4 +19,5 @@
|
|||
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
|
||||
<include file="META-INF/jpa-changelog-authz-2.0.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-authz-2.5.1.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -56,7 +56,7 @@ public class MigrationModelManager {
|
|||
new MigrateTo2_1_0(),
|
||||
new MigrateTo2_2_0(),
|
||||
new MigrateTo2_3_0(),
|
||||
new MigrateTo2_5_0(),
|
||||
new MigrateTo2_5_0()
|
||||
};
|
||||
|
||||
public static void migrate(KeycloakSession session) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.authorization.model.Policy;
|
|||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||
import org.keycloak.authorization.store.PolicyStore;
|
||||
import org.keycloak.authorization.store.ResourceServerStore;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
|
@ -2055,6 +2056,19 @@ public class RepresentationToModel {
|
|||
}
|
||||
|
||||
public static Policy toModel(PolicyRepresentation policy, ResourceServer resourceServer, AuthorizationProvider authorization) {
|
||||
String type = policy.getType();
|
||||
PolicyProvider provider = authorization.getProvider(type);
|
||||
|
||||
if (provider == null) {
|
||||
//TODO: temporary, remove this check on future versions as drools type is now deprecated
|
||||
if ("drools".equalsIgnoreCase(type)) {
|
||||
type = "rules";
|
||||
}
|
||||
if (authorization.getProvider(type) == null) {
|
||||
throw new RuntimeException("Unknown polucy type [" + type + "]. Could not find a provider for this type.");
|
||||
}
|
||||
}
|
||||
|
||||
PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore();
|
||||
Policy existing;
|
||||
|
||||
|
@ -2078,7 +2092,7 @@ public class RepresentationToModel {
|
|||
return existing;
|
||||
}
|
||||
|
||||
Policy model = policyStore.create(policy.getName(), policy.getType(), resourceServer);
|
||||
Policy model = policyStore.create(policy.getName(), type, resourceServer);
|
||||
|
||||
model.setDescription(policy.getDescription());
|
||||
model.setDecisionStrategy(policy.getDecisionStrategy());
|
||||
|
|
|
@ -19,7 +19,10 @@ package org.keycloak.models;
|
|||
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.credential.PasswordUserCredentialModel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
@ -43,15 +46,24 @@ public class UserCredentialModel implements CredentialInput {
|
|||
protected String device;
|
||||
protected String algorithm;
|
||||
|
||||
// Additional context informations
|
||||
protected Map<String, Object> notes = new HashMap<>();
|
||||
|
||||
public UserCredentialModel() {
|
||||
}
|
||||
|
||||
public static UserCredentialModel password(String password) {
|
||||
UserCredentialModel model = new UserCredentialModel();
|
||||
public static PasswordUserCredentialModel password(String password) {
|
||||
return password(password, false);
|
||||
}
|
||||
|
||||
public static PasswordUserCredentialModel password(String password, boolean adminRequest) {
|
||||
PasswordUserCredentialModel model = new PasswordUserCredentialModel();
|
||||
model.setType(PASSWORD);
|
||||
model.setValue(password);
|
||||
model.setAdminRequest(adminRequest);
|
||||
return model;
|
||||
}
|
||||
|
||||
public static UserCredentialModel passwordToken(String passwordToken) {
|
||||
UserCredentialModel model = new UserCredentialModel();
|
||||
model.setType(PASSWORD_TOKEN);
|
||||
|
@ -136,4 +148,16 @@ public class UserCredentialModel implements CredentialInput {
|
|||
public void setAlgorithm(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public void setNote(String key, String value) {
|
||||
this.notes.put(key, value);
|
||||
}
|
||||
|
||||
public void removeNote(String key) {
|
||||
this.notes.remove(key);
|
||||
}
|
||||
|
||||
public Object getNote(String key) {
|
||||
return this.notes.get(key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.models.credential;
|
||||
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PasswordUserCredentialModel extends UserCredentialModel {
|
||||
|
||||
// True if we have password-update request triggered by admin, not by user himself
|
||||
private static final String ADMIN_REQUEST = "adminRequest";
|
||||
|
||||
public boolean isAdminRequest() {
|
||||
Boolean b = (Boolean) this.notes.get(ADMIN_REQUEST);
|
||||
return b!=null && b;
|
||||
}
|
||||
|
||||
public void setAdminRequest(boolean adminRequest) {
|
||||
this.notes.put(ADMIN_REQUEST, adminRequest);
|
||||
}
|
||||
}
|
|
@ -96,7 +96,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
|
|||
credentials.setValue(password);
|
||||
UserModel user = context.getUser();
|
||||
try {
|
||||
context.getSession().userCredentialManager().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password")));
|
||||
context.getSession().userCredentialManager().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password"), false));
|
||||
} catch (Exception me) {
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
|||
}
|
||||
|
||||
try {
|
||||
context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), UserCredentialModel.password(passwordNew));
|
||||
context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), UserCredentialModel.password(passwordNew, false));
|
||||
context.success();
|
||||
} catch (ModelException me) {
|
||||
errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
|
||||
|
|
|
@ -316,6 +316,21 @@ public class TokenManager {
|
|||
}
|
||||
}
|
||||
|
||||
public IDToken verifyIDTokenSignature(KeycloakSession session, RealmModel realm, String encodedIDToken) throws OAuthErrorException {
|
||||
try {
|
||||
JWSInput jws = new JWSInput(encodedIDToken);
|
||||
IDToken idToken;
|
||||
if (!RSAProvider.verify(jws, session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId()))) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
|
||||
}
|
||||
idToken = jws.readJsonContent(IDToken.class);
|
||||
|
||||
return idToken;
|
||||
} catch (JWSInputException e) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken", e);
|
||||
}
|
||||
}
|
||||
|
||||
public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
AccessToken token = initToken(realm, client, user, userSession, clientSession, session.getContext().getUri());
|
||||
for (RoleModel role : requestedRoles) {
|
||||
|
|
|
@ -113,18 +113,11 @@ public class LogoutEndpoint {
|
|||
}
|
||||
|
||||
UserSessionModel userSession = null;
|
||||
boolean error = false;
|
||||
if (encodedIdToken != null) {
|
||||
try {
|
||||
IDToken idToken = tokenManager.verifyIDToken(session, realm, encodedIdToken);
|
||||
IDToken idToken = tokenManager.verifyIDTokenSignature(session, realm, encodedIdToken);
|
||||
userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
|
||||
if (userSession == null) {
|
||||
error = true;
|
||||
}
|
||||
} catch (OAuthErrorException e) {
|
||||
error = true;
|
||||
}
|
||||
if (error) {
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
|
||||
|
|
|
@ -245,9 +245,9 @@ public class SamlProtocol implements LoginProtocol {
|
|||
String logoutPostUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
|
||||
String logoutRedirectUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE);
|
||||
|
||||
if (logoutPostUrl == null) {
|
||||
if (logoutPostUrl == null || logoutPostUrl.trim().isEmpty()) {
|
||||
// if we don't have a redirect uri either, return true and default to the admin url + POST binding
|
||||
if (logoutRedirectUrl == null)
|
||||
if (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
if (SAML_POST_BINDING.equals(bindingType))
|
||||
return true;
|
||||
|
||||
if (logoutRedirectUrl == null)
|
||||
if (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty())
|
||||
return true; // we don't have a redirect binding url, so use post binding
|
||||
|
||||
return false; // redirect binding
|
||||
|
|
|
@ -347,7 +347,8 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
|
||||
if (authResult != null) {
|
||||
String logoutBinding = getBindingType();
|
||||
if (samlClient.forcePostBinding())
|
||||
String postBindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, SamlProtocol.SAML_POST_BINDING);
|
||||
if (samlClient.forcePostBinding() && postBindingUri != null && ! postBindingUri.trim().isEmpty())
|
||||
logoutBinding = SamlProtocol.SAML_POST_BINDING;
|
||||
boolean postBinding = Objects.equals(SamlProtocol.SAML_POST_BINDING, logoutBinding);
|
||||
|
||||
|
|
|
@ -650,7 +650,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
}
|
||||
|
||||
try {
|
||||
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
||||
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password(passwordNew, false));
|
||||
} catch (ModelReadOnlyException mre) {
|
||||
setReferrerOnPage();
|
||||
errorEvent.error(Errors.NOT_ALLOWED);
|
||||
|
|
|
@ -49,6 +49,7 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserLoginFailureModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.credential.PasswordUserCredentialModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
|
@ -776,7 +777,7 @@ public class UsersResource {
|
|||
throw new BadRequestException("Empty password not allowed");
|
||||
}
|
||||
|
||||
UserCredentialModel cred = RepresentationToModel.convertCredential(pass);
|
||||
UserCredentialModel cred = UserCredentialModel.password(pass.getValue(), true);
|
||||
try {
|
||||
session.userCredentialManager().updateCredential(realm, user, cred);
|
||||
} catch (IllegalStateException ise) {
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
{
|
||||
"name": "Only Owner Policy",
|
||||
"description": "Defines that only the resource owner is allowed to do something",
|
||||
"type": "drools",
|
||||
"type": "rules",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
|
|
|
@ -84,6 +84,18 @@
|
|||
</dependencies>
|
||||
|
||||
<build>
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>src/test/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>migration-test/*</include>
|
||||
</includes>
|
||||
</testResource>
|
||||
<testResource>
|
||||
<directory>src/test/resources</directory>
|
||||
</testResource>
|
||||
</testResources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
|
|
@ -33,4 +33,7 @@ public class ProfileAssume {
|
|||
Assume.assumeFalse("Ignoring test as community/preview profile is enabled", !Profile.getName().equals("product"));
|
||||
}
|
||||
|
||||
public static void assumeCommunity() {
|
||||
Assume.assumeTrue("Ignoring test as community profile is not enabled", Profile.getName().equals("community"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,42 @@ public class OAuthClient {
|
|||
|
||||
private Map<String, PublicKey> publicKeys = new HashMap<>();
|
||||
|
||||
public class LogoutUrlBuilder {
|
||||
private final UriBuilder b = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(baseUrl));
|
||||
|
||||
public LogoutUrlBuilder idTokenHint(String idTokenHint) {
|
||||
if (idTokenHint != null) {
|
||||
b.queryParam("id_token_hint", idTokenHint);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LogoutUrlBuilder postLogoutRedirectUri(String redirectUri) {
|
||||
if (redirectUri != null) {
|
||||
b.queryParam("post_logout_redirect_uri", redirectUri);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LogoutUrlBuilder redirectUri(String redirectUri) {
|
||||
if (redirectUri != null) {
|
||||
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LogoutUrlBuilder sessionState(String sessionState) {
|
||||
if (sessionState != null) {
|
||||
b.queryParam("session_state", sessionState);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
return b.build(realm).toString();
|
||||
}
|
||||
}
|
||||
|
||||
public void init(Keycloak adminClient, WebDriver driver) {
|
||||
this.adminClient = adminClient;
|
||||
this.driver = driver;
|
||||
|
@ -341,10 +377,10 @@ public class OAuthClient {
|
|||
}
|
||||
|
||||
|
||||
public HttpResponse doLogout(String refreshToken, String clientSecret) throws IOException {
|
||||
public CloseableHttpResponse doLogout(String refreshToken, String clientSecret) throws IOException {
|
||||
CloseableHttpClient client = new DefaultHttpClient();
|
||||
try {
|
||||
HttpPost post = new HttpPost(getLogoutUrl(null, null));
|
||||
HttpPost post = new HttpPost(getLogoutUrl().build());
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||
if (refreshToken != null) {
|
||||
|
@ -558,15 +594,8 @@ public class OAuthClient {
|
|||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public String getLogoutUrl(String redirectUri, String sessionState) {
|
||||
UriBuilder b = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(baseUrl));
|
||||
if (redirectUri != null) {
|
||||
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
|
||||
}
|
||||
if (sessionState != null) {
|
||||
b.queryParam("session_state", sessionState);
|
||||
}
|
||||
return b.build(realm).toString();
|
||||
public LogoutUrlBuilder getLogoutUrl() {
|
||||
return new LogoutUrlBuilder();
|
||||
}
|
||||
|
||||
public String getResourceOwnerPasswordCredentialGrantUrl() {
|
||||
|
|
|
@ -52,7 +52,7 @@ import static org.junit.Assert.assertTrue;
|
|||
*/
|
||||
public class GenericPolicyManagementTest extends AbstractAuthorizationTest {
|
||||
|
||||
private static final String[] EXPECTED_BUILTIN_POLICY_PROVIDERS = {"test", "user", "role", "drools", "js", "time", "aggregate", "scope", "resource"};
|
||||
private static final String[] EXPECTED_BUILTIN_POLICY_PROVIDERS = {"test", "user", "role", "rules", "js", "time", "aggregate", "scope", "resource"};
|
||||
|
||||
@Before
|
||||
@Override
|
||||
|
|
|
@ -66,7 +66,7 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
String redirectUri = AppPage.baseUrl + "?logout";
|
||||
|
||||
String logoutUrl = oauth.getLogoutUrl(redirectUri, null);
|
||||
String logoutUrl = oauth.getLogoutUrl().redirectUri(redirectUri).build();
|
||||
driver.navigate().to(logoutUrl);
|
||||
|
||||
events.expectLogout(sessionId).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
|
||||
|
@ -89,7 +89,7 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
String sessionId = events.expectLogin().assertEvent().getSessionId();
|
||||
|
||||
String logoutUrl = oauth.getLogoutUrl(null, sessionId);
|
||||
String logoutUrl = oauth.getLogoutUrl().sessionState(sessionId).build();
|
||||
driver.navigate().to(logoutUrl);
|
||||
|
||||
events.expectLogout(sessionId).removeDetail(Details.REDIRECT_URI).assertEvent();
|
||||
|
@ -118,7 +118,7 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
|
|||
events.expectLogin().session(sessionId).removeDetail(Details.USERNAME).assertEvent();
|
||||
|
||||
// Logout session 1 by redirect
|
||||
driver.navigate().to(oauth.getLogoutUrl(AppPage.baseUrl, null));
|
||||
driver.navigate().to(oauth.getLogoutUrl().redirectUri(AppPage.baseUrl).build());
|
||||
events.expectLogout(sessionId).detail(Details.REDIRECT_URI, AppPage.baseUrl).assertEvent();
|
||||
|
||||
// Check session 1 not logged-in
|
||||
|
@ -176,4 +176,28 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
|
|||
rep.setRememberMe(enabled);
|
||||
adminClient.realm("test").update(rep);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logoutSessionWhenLoggedOutByAdmin() {
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
assertTrue(appPage.isCurrent());
|
||||
|
||||
String sessionId = events.expectLogin().assertEvent().getSessionId();
|
||||
|
||||
adminClient.realm("test").logoutAll();
|
||||
|
||||
String logoutUrl = oauth.getLogoutUrl().sessionState(sessionId).build();
|
||||
driver.navigate().to(logoutUrl);
|
||||
|
||||
assertEquals(logoutUrl, driver.getCurrentUrl());
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
assertTrue(appPage.isCurrent());
|
||||
|
||||
String sessionId2 = events.expectLogin().assertEvent().getSessionId();
|
||||
assertNotEquals(sessionId, sessionId2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ public class EmailTest extends AbstractI18NTest {
|
|||
|
||||
@Test
|
||||
public void restPasswordEmailGerman() throws IOException, MessagingException {
|
||||
ProfileAssume.assumePreview();
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
changeUserLocale("de");
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
|||
|
||||
@Test
|
||||
public void acceptLanguageHeader() {
|
||||
ProfileAssume.assumePreview();
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder().build();
|
||||
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
|
||||
|
|
|
@ -25,13 +25,15 @@ import org.keycloak.keys.KeyProvider;
|
|||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.arquillian.migration.Migration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RoleResource;
|
||||
|
@ -47,6 +49,7 @@ import org.keycloak.representations.idm.ClientTemplateRepresentation;
|
|||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
|
||||
import static org.keycloak.testsuite.Assert.assertEquals;
|
||||
import static org.keycloak.testsuite.Assert.assertFalse;
|
||||
import static org.keycloak.testsuite.Assert.assertNames;
|
||||
|
@ -61,8 +64,10 @@ public class MigrationTest extends AbstractKeycloakTest {
|
|||
|
||||
public static final String MIGRATION = "Migration";
|
||||
public static final String MIGRATION2 = "Migration2";
|
||||
public static final String MIGRATION3 = "authorization";
|
||||
private RealmResource migrationRealm;
|
||||
private RealmResource migrationRealm2;
|
||||
private RealmResource migrationRealm3;
|
||||
private RealmResource masterRealm;
|
||||
|
||||
@Override
|
||||
|
@ -74,6 +79,7 @@ public class MigrationTest extends AbstractKeycloakTest {
|
|||
public void beforeMigrationTest() {
|
||||
migrationRealm = adminClient.realms().realm(MIGRATION);
|
||||
migrationRealm2 = adminClient.realms().realm(MIGRATION2);
|
||||
migrationRealm3 = adminClient.realms().realm(MIGRATION3);
|
||||
masterRealm = adminClient.realms().realm(MASTER);
|
||||
|
||||
//add migration realm to testRealmReps to make the migration removed after test
|
||||
|
@ -95,9 +101,9 @@ public class MigrationTest extends AbstractKeycloakTest {
|
|||
@Test
|
||||
@Migration(versionFrom = "2.2.1.Final")
|
||||
public void migration2_2_1Test() {
|
||||
testMigratedData();
|
||||
testMigrationTo2_3_0();
|
||||
testMigrationTo2_5_0();
|
||||
testMigrationTo2_5_1();
|
||||
}
|
||||
|
||||
private void testMigratedData() {
|
||||
|
@ -181,6 +187,10 @@ public class MigrationTest extends AbstractKeycloakTest {
|
|||
testDuplicateEmailSupport(masterRealm, migrationRealm);
|
||||
}
|
||||
|
||||
private void testMigrationTo2_5_1() {
|
||||
testDroolsToRulesPolicyTypeMigration();
|
||||
}
|
||||
|
||||
private void testLdapKerberosMigration_2_5_0() {
|
||||
RealmRepresentation realmRep = migrationRealm2.toRepresentation();
|
||||
List<ComponentRepresentation> components = migrationRealm2.components().query(realmRep.getId(), UserStorageProvider.class.getName());
|
||||
|
@ -215,6 +225,20 @@ public class MigrationTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
private void testDroolsToRulesPolicyTypeMigration() {
|
||||
List<ClientRepresentation> client = migrationRealm3.clients().findByClientId("photoz-restful-api");
|
||||
|
||||
assertEquals(1, client.size());
|
||||
|
||||
ClientRepresentation representation = client.get(0);
|
||||
|
||||
List<PolicyRepresentation> policies = migrationRealm3.clients().get(representation.getId()).authorization().policies().policies();
|
||||
|
||||
List<PolicyRepresentation> migratedRulesPolicies = policies.stream().filter(policyRepresentation -> "rules".equals(policyRepresentation.getType())).collect(Collectors.toList());
|
||||
|
||||
assertEquals(1, migratedRulesPolicies.size());
|
||||
}
|
||||
|
||||
private void testAuthorizationServices(RealmResource... realms) {
|
||||
for (RealmResource realm : realms) {
|
||||
//test setup of authorization services
|
||||
|
|
|
@ -755,7 +755,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
|||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt));
|
||||
|
||||
return sendRequest(oauth.getLogoutUrl(null, null), parameters);
|
||||
return sendRequest(oauth.getLogoutUrl().build(), parameters);
|
||||
}
|
||||
|
||||
private OAuthClient.AccessTokenResponse doClientCredentialsGrantRequest(String signedJwt) throws Exception {
|
||||
|
|
|
@ -17,23 +17,28 @@
|
|||
|
||||
package org.keycloak.testsuite.oauth;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.util.*;
|
||||
|
||||
import java.util.List;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
|
||||
/**
|
||||
|
@ -72,11 +77,12 @@ public class LogoutTest extends AbstractKeycloakTest {
|
|||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
String refreshTokenString = tokenResponse.getRefreshToken();
|
||||
|
||||
HttpResponse response = oauth.doLogout(refreshTokenString, "password");
|
||||
assertEquals(204, response.getStatusLine().getStatusCode());
|
||||
try (CloseableHttpResponse response = oauth.doLogout(refreshTokenString, "password")) {
|
||||
assertThat(response, Matchers.statusCodeIsHC(Status.NO_CONTENT));
|
||||
|
||||
assertNotNull(testingClient.testApp().getAdminLogoutAction());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postLogoutExpiredRefreshToken() throws Exception {
|
||||
|
@ -91,10 +97,83 @@ public class LogoutTest extends AbstractKeycloakTest {
|
|||
adminClient.realm("test").update(RealmBuilder.create().notBefore(Time.currentTime() + 1).build());
|
||||
|
||||
// Logout should succeed with expired refresh token, see KEYCLOAK-3302
|
||||
HttpResponse response = oauth.doLogout(refreshTokenString, "password");
|
||||
assertEquals(204, response.getStatusLine().getStatusCode());
|
||||
try (CloseableHttpResponse response = oauth.doLogout(refreshTokenString, "password")) {
|
||||
assertThat(response, Matchers.statusCodeIsHC(Status.NO_CONTENT));
|
||||
|
||||
assertNotNull(testingClient.testApp().getAdminLogoutAction());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postLogoutWithValidIdToken() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
oauth.clientSessionState("client-session");
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
String idTokenString = tokenResponse.getIdToken();
|
||||
|
||||
String logoutUrl = oauth.getLogoutUrl()
|
||||
.idTokenHint(idTokenString)
|
||||
.postLogoutRedirectUri(AppPage.baseUrl)
|
||||
.build();
|
||||
|
||||
try (CloseableHttpClient c = HttpClientBuilder.create().disableRedirectHandling().build();
|
||||
CloseableHttpResponse response = c.execute(new HttpGet(logoutUrl))) {
|
||||
assertThat(response, Matchers.statusCodeIsHC(Status.FOUND));
|
||||
assertThat(response.getFirstHeader(HttpHeaders.LOCATION).getValue(), is(AppPage.baseUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postLogoutWithExpiredIdToken() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
oauth.clientSessionState("client-session");
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
String idTokenString = tokenResponse.getIdToken();
|
||||
|
||||
// Logout should succeed with expired ID token, see KEYCLOAK-3399
|
||||
setTimeOffset(60 * 60 * 24);
|
||||
|
||||
String logoutUrl = oauth.getLogoutUrl()
|
||||
.idTokenHint(idTokenString)
|
||||
.postLogoutRedirectUri(AppPage.baseUrl)
|
||||
.build();
|
||||
|
||||
try (CloseableHttpClient c = HttpClientBuilder.create().disableRedirectHandling().build();
|
||||
CloseableHttpResponse response = c.execute(new HttpGet(logoutUrl))) {
|
||||
assertThat(response, Matchers.statusCodeIsHC(Status.FOUND));
|
||||
assertThat(response.getFirstHeader(HttpHeaders.LOCATION).getValue(), is(AppPage.baseUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postLogoutWithValidIdTokenWhenLoggedOutByAdmin() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
oauth.clientSessionState("client-session");
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
String idTokenString = tokenResponse.getIdToken();
|
||||
|
||||
adminClient.realm("test").logoutAll();
|
||||
|
||||
// Logout should succeed with user already logged out, see KEYCLOAK-3399
|
||||
String logoutUrl = oauth.getLogoutUrl()
|
||||
.idTokenHint(idTokenString)
|
||||
.postLogoutRedirectUri(AppPage.baseUrl)
|
||||
.build();
|
||||
|
||||
try (CloseableHttpClient c = HttpClientBuilder.create().disableRedirectHandling().build();
|
||||
CloseableHttpResponse response = c.execute(new HttpGet(logoutUrl))) {
|
||||
assertThat(response, Matchers.statusCodeIsHC(Status.FOUND));
|
||||
assertThat(response.getFirstHeader(HttpHeaders.LOCATION).getValue(), is(AppPage.baseUrl));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
{
|
||||
"name": "Only Owner Policy",
|
||||
"description": "Defines that only the resource owner is allowed to do something",
|
||||
"type": "drools",
|
||||
"type": "rules",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -30,7 +30,9 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
private static final String USERNAME = "emily";
|
||||
private static final String PASSWORD = "emily123";
|
||||
private static final String DISABLED_USER = "david";
|
||||
private static final String DISABLED_USER_PASSWORD = "emily123";
|
||||
private static final String DISABLED_USER_PASSWORD = "david123";
|
||||
private static final String NO_EMAIL_USER = "bart";
|
||||
private static final String NO_EMAIL_USER_PASSWORD = "bart123";
|
||||
|
||||
private static final String DEFINITELY_NOT_PASSWORD = "not" + PASSWORD;
|
||||
|
||||
|
@ -102,12 +104,12 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testAdmin() {
|
||||
log.debug("Testing wrong password for user " + ADMIN_USERNAME);
|
||||
log.debug("Testing password for user " + ADMIN_USERNAME);
|
||||
|
||||
driver.navigate().to(getAccountUrl());
|
||||
Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
|
||||
accountLoginPage.login(ADMIN_USERNAME, ADMIN_PASSWORD);
|
||||
Assert.assertEquals("Unexpected error when handling authentication request to identity provider.", accountLoginPage.getInstruction());
|
||||
Assert.assertTrue(profilePage.isCurrent());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -121,6 +123,16 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
testUserGroups();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExistingUserWithNoEmailLogIn() {
|
||||
log.debug("Testing correct password, but no e-mail provided");
|
||||
|
||||
driver.navigate().to(getAccountUrl());
|
||||
Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
|
||||
accountLoginPage.login(NO_EMAIL_USER, NO_EMAIL_USER_PASSWORD);
|
||||
Assert.assertTrue(profilePage.isCurrent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteSSSDFederationProvider() {
|
||||
log.debug("Testing correct password");
|
||||
|
|
|
@ -489,7 +489,7 @@
|
|||
<profile>
|
||||
<id>auth-server-migration</id>
|
||||
<properties>
|
||||
<migration.import.file>src/test/resources/migration-test/migration-realm-${migrated.auth.server.version}.json</migration.import.file>
|
||||
<migration.import.file>target/test-classes/migration-test/migration-realm-${migrated.auth.server.version}.json</migration.import.file>
|
||||
<migration.import.props.previous>
|
||||
-Dkeycloak.migration.action=import
|
||||
-Dkeycloak.migration.provider=singleFile
|
||||
|
@ -572,7 +572,7 @@
|
|||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<migration.import.file>src/test/resources/migration-test/migration-realm-${migrated.auth.server.version}.json</migration.import.file>
|
||||
<migration.import.file>target/test-classes/migration-test/migration-realm-${migrated.auth.server.version}.json</migration.import.file>
|
||||
<migration.import.properties>
|
||||
-Dkeycloak.migration.action=import
|
||||
-Dkeycloak.migration.provider=singleFile
|
||||
|
@ -621,7 +621,7 @@
|
|||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<migration.import.file>src/test/resources/migration-test/migration-realm-${migrated.version.import.file.suffix}.json</migration.import.file>
|
||||
<migration.import.file>target/test-classes/migration-test/migration-realm-${migrated.version.import.file.suffix}.json</migration.import.file>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
|
|
|
@ -715,7 +715,7 @@ public class LDAPProvidersIntegrationTest {
|
|||
|
||||
}
|
||||
try {
|
||||
UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1");
|
||||
UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1", true);
|
||||
session.userCredentialManager().updateCredential(appRealm, user, cred);
|
||||
Assert.fail("should fail");
|
||||
} catch (ModelReadOnlyException e) {
|
||||
|
@ -856,7 +856,7 @@ public class LDAPProvidersIntegrationTest {
|
|||
Assert.assertNotNull(user.getFederationLink());
|
||||
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
|
||||
|
||||
UserCredentialModel cred = UserCredentialModel.password("Candycand1");
|
||||
UserCredentialModel cred = UserCredentialModel.password("Candycand1", true);
|
||||
session.userCredentialManager().updateCredential(appRealm, user, cred);
|
||||
CredentialModel userCredentialValueModel = session.userCredentialManager().getStoredCredentialsByType(appRealm, user, CredentialModel.PASSWORD).get(0);
|
||||
Assert.assertEquals(UserCredentialModel.PASSWORD, userCredentialValueModel.getType());
|
||||
|
|
|
@ -128,7 +128,7 @@ public class LDAPTestUtils {
|
|||
}
|
||||
|
||||
public static void updateLDAPPassword(LDAPStorageProvider ldapProvider, LDAPObject ldapUser, String password) {
|
||||
ldapProvider.getLdapIdentityStore().updatePassword(ldapUser, password);
|
||||
ldapProvider.getLdapIdentityStore().updatePassword(ldapUser, password, null);
|
||||
|
||||
// Enable MSAD user through userAccountControls
|
||||
if (ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) {
|
||||
|
|
|
@ -150,6 +150,7 @@ invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0
|
|||
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
||||
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
||||
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
|
||||
|
||||
locale_ca=Catal\u00E0
|
||||
locale_de=Deutsch
|
||||
|
|
|
@ -1142,7 +1142,7 @@ authz-policy-time-minute=Minute
|
|||
authz-policy-time-minute.tooltip=Defines the minute which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current minute is between or equal to the two values you provided.
|
||||
|
||||
# Authz Drools Policy Detail
|
||||
authz-add-drools-policy=Add Drools Policy
|
||||
authz-add-drools-policy=Add Rules Policy
|
||||
authz-policy-drools-maven-artifact-resolve=Resolve
|
||||
authz-policy-drools-maven-artifact=Policy Maven Artifact
|
||||
authz-policy-drools-maven-artifact.tooltip=A Maven GAV pointing to an artifact from where the rules would be loaded from. Once you have provided the GAV, you can click *Resolve* to load both *Module* and *Session* fields.
|
||||
|
|
|
@ -1107,7 +1107,7 @@ authz-policy-time-minute=分
|
|||
authz-policy-time-minute.tooltip=ポリシーが許可される分を定義します。2番目のフィールドに値を入力して範囲を指定することもできます。この場合、現在の分が指定した2つの値の間にあるか、等しい場合のみ許可されます。
|
||||
|
||||
# Authz Drools Policy Detail
|
||||
authz-add-drools-policy=Drools ポリシーの追加
|
||||
authz-add-drools-policy=Rules ポリシーの追加
|
||||
authz-policy-drools-maven-artifact-resolve=解決
|
||||
authz-policy-drools-maven-artifact=ポリシー Maven アーティファクト
|
||||
authz-policy-drools-maven-artifact.tooltip=ルールの読み込む先となるアーティファクトを示す Maven GAV を設定します。GAV を提供し 「解決」 をクリックすることで、 「モジュール」 と 「セッション」 フィールドを読み込みます。
|
||||
|
|
|
@ -1102,7 +1102,7 @@ authz-policy-time-minute=Minut\u0117
|
|||
authz-policy-time-minute.tooltip=Nurodykite minut\u0119 iki kurios \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei minut\u0117 patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai.
|
||||
|
||||
# Authz Drools Policy Detail
|
||||
authz-add-drools-policy=Prid\u0117ti Drools taisykl\u0119
|
||||
authz-add-drools-policy=Prid\u0117ti Rules taisykl\u0119
|
||||
authz-policy-drools-maven-artifact-resolve=I\u0161spr\u0119sti
|
||||
authz-policy-drools-maven-artifact=Maven taisykl\u0117s artefaktas
|
||||
authz-policy-drools-maven-artifact.tooltip=Nuoroda \u012F Maven GAV artifakt\u0105 kuriame apra\u0161ytos taisykl\u0117s. Kai tik nurodysite GAV, galite paspausti *I\u0161spr\u0119sti* tam kad \u012Fkelti *Modulis* ir *Sesija* laukus.
|
||||
|
|
|
@ -1059,7 +1059,7 @@ authz-policy-time-not-on-after=Ikke p\u00E5 eller etter
|
|||
authz-policy-time-not-on-after.tooltip=Definerer tiden etter en policy M\u00C5 IKKE innvilges. Denne innvilges kun om gjeldende dato/tid er f\u00F8r eller lik denne verdien.
|
||||
|
||||
# Authz Drools Policy Detail
|
||||
authz-add-drools-policy=Legg til Drools policy
|
||||
authz-add-drools-policy=Legg til Rules policy
|
||||
authz-policy-drools-maven-artifact-resolve=L\u00F8s
|
||||
authz-policy-drools-maven-artifact=Policy for Maven artefakt.
|
||||
authz-policy-drools-maven-artifact.tooltip=Et Maven GAV som peker til et artefakt hvor reglene vil bli lastet fra. Med en gang du har gitt GAV kan du klikke *L\u00F8s* for \u00E5 laste felter for b\u00E5de *Modul* og *Sesjon*
|
||||
|
|
|
@ -771,7 +771,7 @@ authz-add-time-policy=Adicionar política de tempo
|
|||
authz-policy-time-not-on-after=Não em ou depois
|
||||
|
||||
# Authz Drools Policy Detail
|
||||
authz-add-drools-policy=Adicionar política Drools
|
||||
authz-add-drools-policy=Adicionar política Rules
|
||||
authz-policy-drools-maven-artifact-resolve=Resolver
|
||||
authz-policy-drools-maven-artifact=Artefato maven de política
|
||||
authz-policy-drools-module=Módulo
|
||||
|
|
|
@ -6,6 +6,7 @@ invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0
|
|||
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
||||
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
||||
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
|
||||
|
||||
ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")".
|
||||
ldapErrorConnectionTimeoutNotNumber=Connection Timeout must be a number
|
||||
|
|
|
@ -175,7 +175,7 @@ module.config(['$routeProvider', function ($routeProvider) {
|
|||
}
|
||||
},
|
||||
controller: 'ResourceServerPolicyCtrl'
|
||||
}).when('/realms/:realm/clients/:client/authz/resource-server/policy/drools/create', {
|
||||
}).when('/realms/:realm/clients/:client/authz/resource-server/policy/rules/create', {
|
||||
templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-drools-detail.html',
|
||||
resolve: {
|
||||
realm: function (RealmLoader) {
|
||||
|
@ -186,7 +186,7 @@ module.config(['$routeProvider', function ($routeProvider) {
|
|||
}
|
||||
},
|
||||
controller: 'ResourceServerPolicyDroolsDetailCtrl'
|
||||
}).when('/realms/:realm/clients/:client/authz/resource-server/policy/drools/:id', {
|
||||
}).when('/realms/:realm/clients/:client/authz/resource-server/policy/rules/:id', {
|
||||
templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-drools-detail.html',
|
||||
resolve: {
|
||||
realm: function (RealmLoader) {
|
||||
|
|
|
@ -743,7 +743,7 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route
|
|||
module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http, $route, realm, client, PolicyController) {
|
||||
PolicyController.onInit({
|
||||
getPolicyType : function() {
|
||||
return "drools";
|
||||
return "rules";
|
||||
},
|
||||
|
||||
onInit : function() {
|
||||
|
@ -754,7 +754,7 @@ module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http
|
|||
policy = $scope.policy;
|
||||
}
|
||||
|
||||
$http.post(authUrl + '/admin/realms/'+ $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/policy/drools/resolveModules'
|
||||
$http.post(authUrl + '/admin/realms/'+ $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/policy/rules/resolveModules'
|
||||
, policy).success(function(data) {
|
||||
$scope.drools.moduleNames = data;
|
||||
$scope.resolveSessions();
|
||||
|
@ -762,7 +762,7 @@ module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http
|
|||
}
|
||||
|
||||
$scope.resolveSessions = function() {
|
||||
$http.post(authUrl + '/admin/realms/'+ $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/policy/drools/resolveSessions'
|
||||
$http.post(authUrl + '/admin/realms/'+ $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/policy/rules/resolveSessions'
|
||||
, $scope.policy).success(function(data) {
|
||||
$scope.drools.moduleSessions = data;
|
||||
});
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
<table class="table kc-authz-table-expanded table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Associated Permissions</th>
|
||||
<th>Associated Policies</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' | translate}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy">{{:: 'authz-policies' | translate}}</a></li>
|
||||
<li data-ng-show="create">{{:: 'authz-add-drools-policy' | translate}}</li>
|
||||
<li data-ng-hide="create">Drools</li>
|
||||
<li data-ng-hide="create">Rules</li>
|
||||
<li data-ng-hide="create">{{originalPolicy.name}}</li>
|
||||
</ol>
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
<table class="table kc-authz-table-expanded table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Dependent Permissions</th>
|
||||
<th>Dependent Permissions and Policies</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -91,7 +91,7 @@
|
|||
<span data-ng-show="policy.dependentPolicies && !policy.dependentPolicies.length">{{:: 'authz-no-permission-assigned' | translate}}</span>
|
||||
<ul ng-repeat="dep in policy.dependentPolicies" data-ng-show="policy.dependentPolicies.length > 0">
|
||||
<li>
|
||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{dep.type}}/{{dep.id}}">{{dep.name}}</a>
|
||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/{{dep.type == 'scope' || dep.type == 'resource' ? 'permission' : 'policy'}}/{{dep.type}}/{{dep.id}}">{{dep.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
|
|
|
@ -168,6 +168,7 @@ invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0
|
|||
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
||||
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
||||
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
|
||||
|
||||
failedToProcessResponseMessage=Failed to process response
|
||||
httpsRequiredMessage=HTTPS required
|
||||
|
|
Loading…
Reference in a new issue