mirror of
https://github.com/bmlong137/keycloak-group-password-policy.git
synced 2025-09-10 22:21:07 +00:00
refactored to support required action multiplexing/reusability
This commit is contained in:
5
pom.xml
5
pom.xml
@@ -33,6 +33,11 @@
|
|||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<version>3.4.0.Final</version>
|
<version>3.4.0.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-services</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Julian Picht
|
* Copyright 2019 Julian Picht
|
||||||
|
* Copyright 2021 Brian Long
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,29 +18,23 @@
|
|||||||
package com.github.jpicht.keycloak.policy;
|
package com.github.jpicht.keycloak.policy;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.policy.PasswordPolicyConfigException;
|
|
||||||
|
|
||||||
public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
|
public class GroupPasswordPolicyFinder {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(GroupPasswordPolicyProvider.class);
|
private static final Logger logger = Logger.getLogger(GroupPasswordPolicyFinder.class);
|
||||||
|
|
||||||
public GroupPasswordPolicyProvider(KeycloakSession session) {
|
public List<String> findPolicies(RealmModel realm, UserModel user) {
|
||||||
super(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LinkedList<String> findPolicies(RealmModel realm, UserModel user) {
|
|
||||||
// First get the name of the attribute
|
// First get the name of the attribute
|
||||||
String groupAttribute = realm.getPasswordPolicy().getPolicyConfig(GroupPasswordPolicyProviderFactory.ID);
|
String groupAttribute = realm.getPasswordPolicy().getPolicyConfig(GroupPasswordPolicyProviderFactory.ID);
|
||||||
logger.debugf("groupAttribute %s", groupAttribute);
|
logger.debugf("groupAttribute: %s", groupAttribute);
|
||||||
logger.debugf("user %s", user.getUsername());
|
logger.debugf("user: %s", user.getUsername());
|
||||||
|
|
||||||
LinkedList<String> policyDefinitions = new LinkedList<>();
|
LinkedList<String> policyDefinitions = new LinkedList<>();
|
||||||
|
|
||||||
@@ -47,7 +42,7 @@ public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
|
|||||||
user.getGroupsStream().forEach(new Consumer<GroupModel>() {
|
user.getGroupsStream().forEach(new Consumer<GroupModel>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(GroupModel group) {
|
public void accept(GroupModel group) {
|
||||||
logger.debugf("group %s", group.getName());
|
logger.debugf("group: %s", group.getName());
|
||||||
group.getAttributeStream(groupAttribute).forEach(new Consumer<String>() {
|
group.getAttributeStream(groupAttribute).forEach(new Consumer<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(String policyString) {
|
public void accept(String policyString) {
|
||||||
@@ -60,16 +55,5 @@ public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
|
|||||||
|
|
||||||
return policyDefinitions;
|
return policyDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object parseConfig(String value) {
|
|
||||||
if (value == null || value.isEmpty()) {
|
|
||||||
throw new PasswordPolicyConfigException("Attribute name cannot be blank");
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -17,33 +17,28 @@
|
|||||||
package com.github.jpicht.keycloak.policy;
|
package com.github.jpicht.keycloak.policy;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.policy.PasswordPolicyConfigException;
|
|
||||||
|
|
||||||
public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
|
public class GroupPasswordPolicyFinder {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(GroupPasswordPolicyProvider.class);
|
private static final Logger logger = Logger.getLogger(GroupPasswordPolicyFinder.class);
|
||||||
|
|
||||||
public GroupPasswordPolicyProvider(KeycloakSession session) {
|
protected List<String> findPolicies(RealmModel realm, UserModel user) {
|
||||||
super(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LinkedList<String> findPolicies(RealmModel realm, UserModel user) {
|
|
||||||
// First get the name of the attribute
|
// First get the name of the attribute
|
||||||
String groupAttribute = realm.getPasswordPolicy().getPolicyConfig(GroupPasswordPolicyProviderFactory.ID);
|
String groupAttribute = realm.getPasswordPolicy().getPolicyConfig(GroupPasswordPolicyProviderFactory.ID);
|
||||||
logger.debugf("groupAttribute %s", groupAttribute);
|
logger.debugf("groupAttribute: %s", groupAttribute);
|
||||||
logger.debugf("user %s", user.getUsername());
|
logger.debugf("user: %s", user.getUsername());
|
||||||
|
|
||||||
LinkedList<String> policyDefinitions = new LinkedList<>();
|
LinkedList<String> policyDefinitions = new LinkedList<>();
|
||||||
|
|
||||||
// Iterate groups and collect policy strings
|
// Iterate groups and collect policy strings
|
||||||
for (GroupModel group : user.getGroups()) {
|
for (GroupModel group : user.getGroups()) {
|
||||||
logger.debugf("group %s", group.getName());
|
logger.debugf("group: %s", group.getName());
|
||||||
for (String policyString : group.getAttribute(groupAttribute)) {
|
for (String policyString : group.getAttribute(groupAttribute)) {
|
||||||
logger.infof("adding group password policy: %s", policyString);
|
logger.infof("adding group password policy: %s", policyString);
|
||||||
policyDefinitions.add(policyString);
|
policyDefinitions.add(policyString);
|
||||||
@@ -52,16 +47,5 @@ public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
|
|||||||
|
|
||||||
return policyDefinitions;
|
return policyDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object parseConfig(String value) {
|
|
||||||
if (value == null || value.isEmpty()) {
|
|
||||||
throw new PasswordPolicyConfigException("Attribute name cannot be blank");
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Julian Picht
|
||||||
|
* Copyright 2021 Brian Long
|
||||||
|
*
|
||||||
|
* 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 com.github.jpicht.keycloak.policy;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.policy.PasswordPolicyConfigException;
|
||||||
|
|
||||||
|
public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
|
||||||
|
|
||||||
|
private GroupPasswordPolicyFinder finder = new GroupPasswordPolicyFinder();
|
||||||
|
|
||||||
|
public GroupPasswordPolicyProvider(KeycloakSession session) {
|
||||||
|
super(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> findPolicies(RealmModel realm, UserModel user) {
|
||||||
|
return this.finder.findPolicies(realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
throw new PasswordPolicyConfigException("Attribute name cannot be blank");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -39,7 +39,7 @@ abstract public class PolicyProviderMultiplexer implements PasswordPolicyProvide
|
|||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected LinkedList<String> findPolicies(RealmModel realm, UserModel user);
|
protected abstract List<String> findPolicies(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(String username, String password) {
|
public PolicyError validate(String username, String password) {
|
||||||
|
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Brian Long
|
||||||
|
*
|
||||||
|
* 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 com.github.jpicht.keycloak.policy;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.credential.CredentialProvider;
|
||||||
|
import org.keycloak.credential.PasswordCredentialProvider;
|
||||||
|
import org.keycloak.credential.PasswordCredentialProviderFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author brian@inteligr8.com
|
||||||
|
*/
|
||||||
|
abstract class RequiredActionMultiplexer implements RequiredActionProvider {
|
||||||
|
|
||||||
|
private final Logger logger = Logger.getLogger(RequiredActionMultiplexer.class);
|
||||||
|
|
||||||
|
protected abstract int findDaysToExpire(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a re-implementation of what is found in the default
|
||||||
|
* implementation in Keycloak. It just makes days-to-expire abstract so it
|
||||||
|
* can be implemented any number of ways.
|
||||||
|
*
|
||||||
|
* https://github.com/keycloak/keycloak/blob/af3b573d196af882dfb25cdccb98361746e85481/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java#L67
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void evaluateTriggers(RequiredActionContext context) {
|
||||||
|
this.logger.tracef("evaluateTriggers({})", context.getUser() != null ? context.getUser().getUsername() : null);
|
||||||
|
|
||||||
|
int daysToExpirePassword = this.findDaysToExpire(context.getRealm(), context.getUser());
|
||||||
|
if (daysToExpirePassword > -1) {
|
||||||
|
PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider)context.getSession()
|
||||||
|
.getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID);
|
||||||
|
CredentialModel password = passwordProvider.getPassword(context.getRealm(), context.getUser());
|
||||||
|
if (password != null) {
|
||||||
|
if(password.getCreatedDate() == null) {
|
||||||
|
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
|
logger.debug("User is required to update password");
|
||||||
|
} else {
|
||||||
|
long timeElapsed = Time.currentTimeMillis() - password.getCreatedDate();
|
||||||
|
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
|
||||||
|
|
||||||
|
if(timeElapsed > timeToExpire) {
|
||||||
|
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
|
logger.debug("User is required to update password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requiredActionChallenge(RequiredActionContext context) {
|
||||||
|
this.logger.tracef("requiredActionChallenge({})", context.getUser() != null ? context.getUser().getUsername() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processAction(RequiredActionContext context) {
|
||||||
|
this.logger.tracef("processAction({})", context.getUser() != null ? context.getUser().getUsername() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user