mirror of
https://github.com/bmlong137/keycloak-group-password-policy.git
synced 2025-09-11 06:21:10 +00:00
fixes/refactoring after testing
This commit is contained in:
14
README.md
14
README.md
@@ -21,11 +21,17 @@ by navigating to the "Authentication" menu item in the vertical menu on the left
|
|||||||
realm's user interface. You will then need to navigate to the "Password Policy" tab along the
|
realm's user interface. You will then need to navigate to the "Password Policy" tab along the
|
||||||
menu of tabs on the top of the page.
|
menu of tabs on the top of the page.
|
||||||
|
|
||||||
This interface provides you the OOTB ability to specify password policies for all users. This
|
This interface provides you the OOTB ability to specify password policies for **all** users.
|
||||||
is still true with the plugin installed. You will also have an additional option: **Group
|
This is still true with the plugin installed. You will now have an additional option: **Group
|
||||||
Policy**. To use the plugin, you must add that password policy. The "Policy Value" should be
|
Policy**. To use the plugin, you must add that password policy. The "Policy Value" should be
|
||||||
set to the ID we came up with earlier: `passwordPolicy`.
|
set to the ID we came up with earlier: `passwordPolicy`.
|
||||||
|
|
||||||
|
If you intend to use group-specific password expiration (`forceExpiredPasswordChange`), you will
|
||||||
|
need to perform an additional step in the configuration. In the same "Authentication" section,
|
||||||
|
navigate to the "Required Actions" tab along the menu of tabs on the top of the page. Use the
|
||||||
|
"Register" button to add the "Group-based Expired Password" action. Move it up in the order to
|
||||||
|
after the "Update Password" action.
|
||||||
|
|
||||||
At this point, you will need to add an attribute (with key `passwordPolicy`) to each group you
|
At this point, you will need to add an attribute (with key `passwordPolicy`) to each group you
|
||||||
want to have additional password policies. The format of that text is defined by Keycloak
|
want to have additional password policies. The format of that text is defined by Keycloak
|
||||||
documentation and covered in the section below.
|
documentation and covered in the section below.
|
||||||
@@ -50,7 +56,8 @@ The [policies provided with KeyCloak](https://www.keycloak.org/docs/6.0/server_a
|
|||||||
| `regexPattern(string)` | regular expression | ✓ |
|
| `regexPattern(string)` | regular expression | ✓ |
|
||||||
| `notUsername()` | | ✓ |
|
| `notUsername()` | | ✓ |
|
||||||
| `passwordBlacklist(string)` | file name | - |
|
| `passwordBlacklist(string)` | file name | - |
|
||||||
| `passwordHistory(int)` | number of last used passwords to disallow | - |
|
| `passwordHistory(int)` | number of last used passwords to disallow | ✓ |
|
||||||
|
| `forceExpiredPasswordChange(string)` | number of days to expire password after | ✓ |
|
||||||
|
|
||||||
On the realm model the password policy attribute is also used for other purposes.
|
On the realm model the password policy attribute is also used for other purposes.
|
||||||
There are some registered "policies", that do not actually implement a policy that
|
There are some registered "policies", that do not actually implement a policy that
|
||||||
@@ -60,7 +67,6 @@ If these currently work is completely untested.
|
|||||||
|
|
||||||
| Identifier | Description | Tested |
|
| Identifier | Description | Tested |
|
||||||
| ------------- |:------------------------------------ | ------ |
|
| ------------- |:------------------------------------ | ------ |
|
||||||
| `forceExpiredPasswordChange(string)` | number of days to expire password after | - |
|
|
||||||
| `hashAlgorithm(string)` | hash algorithm to use when hashing the password | - |
|
| `hashAlgorithm(string)` | hash algorithm to use when hashing the password | - |
|
||||||
| `hashIterations(int)` | number of hash iterations | - |
|
| `hashIterations(int)` | number of hash iterations | - |
|
||||||
|
|
||||||
|
6
pom.xml
6
pom.xml
@@ -73,6 +73,12 @@
|
|||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<classifier>keycloak-v${keycloak.majorVersion}</classifier>
|
<classifier>keycloak-v${keycloak.majorVersion}</classifier>
|
||||||
|
<archive>
|
||||||
|
<manifestEntries>
|
||||||
|
<!-- This is required in order for the classloader of this JAR to access the specified library at runtime -->
|
||||||
|
<Dependencies>org.keycloak.keycloak-services</Dependencies>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
@@ -29,12 +29,12 @@ import com.google.auto.service.AutoService;
|
|||||||
* @author brian@inteligr8.com
|
* @author brian@inteligr8.com
|
||||||
*/
|
*/
|
||||||
@AutoService(RequiredActionFactory.class)
|
@AutoService(RequiredActionFactory.class)
|
||||||
public class GroupRequiredActionFactory implements RequiredActionFactory {
|
public class GroupExpiredPasswordRequiredActionFactory implements RequiredActionFactory {
|
||||||
|
|
||||||
private final Logger logger = Logger.getLogger(GroupRequiredActionFactory.class);
|
private final Logger logger = Logger.getLogger(GroupExpiredPasswordRequiredActionFactory.class);
|
||||||
|
|
||||||
private static final String ID = "groupRequiredAction";
|
private static final String ID = "groupRequiredAction";
|
||||||
private static final String DISPLAY = "Group Action";
|
private static final String DISPLAY = "Group-based Expired Password";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@@ -43,7 +43,8 @@ public class GroupRequiredActionFactory implements RequiredActionFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RequiredActionProvider create(KeycloakSession session) {
|
public RequiredActionProvider create(KeycloakSession session) {
|
||||||
return new GroupRequiredActionProvider(session);
|
this.logger.trace("create()");
|
||||||
|
return new GroupExpiredPasswordRequiredActionProvider(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@@ -27,25 +27,31 @@ import org.keycloak.models.UserModel;
|
|||||||
/**
|
/**
|
||||||
* @author brian@inteligr8.com
|
* @author brian@inteligr8.com
|
||||||
*/
|
*/
|
||||||
public class GroupRequiredActionProvider extends RequiredActionMultiplexer {
|
public class GroupExpiredPasswordRequiredActionProvider extends RequiredActionMultiplexer {
|
||||||
|
|
||||||
private final Logger logger = Logger.getLogger(RequiredActionMultiplexer.class);
|
private final Logger logger = Logger.getLogger(RequiredActionMultiplexer.class);
|
||||||
private final GroupPasswordPolicyFinder finder = new GroupPasswordPolicyFinder();
|
private final GroupPasswordPolicyFinder finder = new GroupPasswordPolicyFinder();
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
|
||||||
public GroupRequiredActionProvider(KeycloakSession session) {
|
public GroupExpiredPasswordRequiredActionProvider(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int findDaysToExpire(RealmModel realm, UserModel user) {
|
protected int findDaysToExpire(RealmModel realm, UserModel user) {
|
||||||
|
if (this.logger.isTraceEnabled())
|
||||||
|
this.logger.tracef("findDaysToExpire(%s, %s)", realm == null ? null : realm.getName(), user == null ? null : user.getId());
|
||||||
|
|
||||||
List<String> policyStrs = this.finder.findPolicies(realm, user);
|
List<String> policyStrs = this.finder.findPolicies(realm, user);
|
||||||
if (policyStrs == null || policyStrs.isEmpty())
|
if (policyStrs == null || policyStrs.isEmpty())
|
||||||
return -1;
|
return -1;
|
||||||
|
this.logger.debugf("found policies: [%s]", policyStrs.toString());
|
||||||
|
|
||||||
Integer minDaysToExpire = null;
|
Integer minDaysToExpire = null;
|
||||||
|
|
||||||
for (String policyStr : policyStrs) {
|
for (String policyStr : policyStrs) {
|
||||||
|
this.logger.tracef("inspecting policy: %s", policyStr);
|
||||||
|
|
||||||
PasswordPolicy policy = PasswordPolicy.parse(this.session, policyStr);
|
PasswordPolicy policy = PasswordPolicy.parse(this.session, policyStr);
|
||||||
int daysToExpire = policy.getDaysToExpirePassword();
|
int daysToExpire = policy.getDaysToExpirePassword();
|
||||||
if (daysToExpire < 0)
|
if (daysToExpire < 0)
|
||||||
@@ -63,6 +69,7 @@ public class GroupRequiredActionProvider extends RequiredActionMultiplexer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.debugf("determined password expiration policy: %d days", minDaysToExpire);
|
||||||
return minDaysToExpire == null ? -1 : minDaysToExpire;
|
return minDaysToExpire == null ? -1 : minDaysToExpire;
|
||||||
}
|
}
|
||||||
|
|
@@ -17,6 +17,8 @@
|
|||||||
package com.github.jpicht.keycloak.policy;
|
package com.github.jpicht.keycloak.policy;
|
||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
@@ -26,6 +28,8 @@ import org.keycloak.policy.PasswordPolicyProviderFactory;
|
|||||||
@AutoService(PasswordPolicyProviderFactory.class)
|
@AutoService(PasswordPolicyProviderFactory.class)
|
||||||
public class GroupPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
public class GroupPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
private final Logger logger = Logger.getLogger(GroupPasswordPolicyProviderFactory.class);
|
||||||
|
|
||||||
static final String ID = "groupPasswordPolicy";
|
static final String ID = "groupPasswordPolicy";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,6 +44,7 @@ public class GroupPasswordPolicyProviderFactory implements PasswordPolicyProvide
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
|
this.logger.trace("init()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -68,5 +73,6 @@ public class GroupPasswordPolicyProviderFactory implements PasswordPolicyProvide
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
this.logger.trace("close()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@ package com.github.jpicht.keycloak.policy;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.authentication.InitiatedActionSupport;
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
@@ -38,6 +39,11 @@ abstract class RequiredActionMultiplexer implements RequiredActionProvider {
|
|||||||
|
|
||||||
protected abstract int findDaysToExpire(RealmModel realm, UserModel user);
|
protected abstract int findDaysToExpire(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InitiatedActionSupport initiatedActionSupport() {
|
||||||
|
return InitiatedActionSupport.SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a re-implementation of what is found in the default
|
* This is a re-implementation of what is found in the default
|
||||||
* implementation in Keycloak. It just makes days-to-expire abstract so it
|
* implementation in Keycloak. It just makes days-to-expire abstract so it
|
||||||
@@ -51,20 +57,26 @@ abstract class RequiredActionMultiplexer implements RequiredActionProvider {
|
|||||||
|
|
||||||
int daysToExpirePassword = this.findDaysToExpire(context.getRealm(), context.getUser());
|
int daysToExpirePassword = this.findDaysToExpire(context.getRealm(), context.getUser());
|
||||||
if (daysToExpirePassword > -1) {
|
if (daysToExpirePassword > -1) {
|
||||||
|
this.logger.debugf("Found password expiration: %d days", daysToExpirePassword);
|
||||||
|
|
||||||
PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider)context.getSession()
|
PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider)context.getSession()
|
||||||
.getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID);
|
.getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID);
|
||||||
CredentialModel password = passwordProvider.getPassword(context.getRealm(), context.getUser());
|
CredentialModel password = passwordProvider.getPassword(context.getRealm(), context.getUser());
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
|
this.logger.tracef("Found password credentials; created: %d ms", password.getCreatedDate());
|
||||||
|
|
||||||
if(password.getCreatedDate() == null) {
|
if(password.getCreatedDate() == null) {
|
||||||
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
logger.debug("User is required to update password");
|
this.logger.debug("User is required to update password");
|
||||||
} else {
|
} else {
|
||||||
long timeElapsed = Time.currentTimeMillis() - password.getCreatedDate();
|
long timeElapsed = Time.currentTimeMillis() - password.getCreatedDate();
|
||||||
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
|
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
|
||||||
|
|
||||||
if(timeElapsed > timeToExpire) {
|
if(timeElapsed > timeToExpire) {
|
||||||
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
logger.debug("User is required to update password");
|
this.logger.debug("User is required to update password");
|
||||||
|
} else {
|
||||||
|
this.logger.tracef("Password credentials expire in %d ms", timeToExpire);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user