diff --git a/pom.xml b/pom.xml
index 72f52ba..163d564 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,11 @@
jboss-logging
3.4.0.Final
+
+ org.keycloak
+ keycloak-services
+ ${keycloak.version}
+
org.keycloak
keycloak-core
diff --git a/src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java b/src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyFinder.java
similarity index 67%
rename from src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java
rename to src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyFinder.java
index bc42684..b203f61 100644
--- a/src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java
+++ b/src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyFinder.java
@@ -1,5 +1,6 @@
/*
* 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.
@@ -17,29 +18,23 @@
package com.github.jpicht.keycloak.policy;
import java.util.LinkedList;
+import java.util.List;
import java.util.function.Consumer;
import org.jboss.logging.Logger;
import org.keycloak.models.GroupModel;
-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 {
+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) {
- super(session);
- }
-
- @Override
- protected LinkedList findPolicies(RealmModel realm, UserModel user) {
+ public List findPolicies(RealmModel realm, UserModel user) {
// First get the name of the attribute
String groupAttribute = realm.getPasswordPolicy().getPolicyConfig(GroupPasswordPolicyProviderFactory.ID);
- logger.debugf("groupAttribute %s", groupAttribute);
- logger.debugf("user %s", user.getUsername());
+ logger.debugf("groupAttribute: %s", groupAttribute);
+ logger.debugf("user: %s", user.getUsername());
LinkedList policyDefinitions = new LinkedList<>();
@@ -47,7 +42,7 @@ public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
user.getGroupsStream().forEach(new Consumer() {
@Override
public void accept(GroupModel group) {
- logger.debugf("group %s", group.getName());
+ logger.debugf("group: %s", group.getName());
group.getAttributeStream(groupAttribute).forEach(new Consumer() {
@Override
public void accept(String policyString) {
@@ -60,16 +55,5 @@ public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
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() {
- }
+
}
diff --git a/src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java b/src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyFinder.java
similarity index 63%
rename from src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java
rename to src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyFinder.java
index 0da4575..5c1f41e 100644
--- a/src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java
+++ b/src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyFinder.java
@@ -17,33 +17,28 @@
package com.github.jpicht.keycloak.policy;
import java.util.LinkedList;
+import java.util.List;
+
import org.jboss.logging.Logger;
import org.keycloak.models.GroupModel;
-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 {
+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) {
- super(session);
- }
-
- @Override
- protected LinkedList findPolicies(RealmModel realm, UserModel user) {
+ protected List findPolicies(RealmModel realm, UserModel user) {
// First get the name of the attribute
String groupAttribute = realm.getPasswordPolicy().getPolicyConfig(GroupPasswordPolicyProviderFactory.ID);
- logger.debugf("groupAttribute %s", groupAttribute);
- logger.debugf("user %s", user.getUsername());
+ logger.debugf("groupAttribute: %s", groupAttribute);
+ logger.debugf("user: %s", user.getUsername());
LinkedList policyDefinitions = new LinkedList<>();
// Iterate groups and collect policy strings
for (GroupModel group : user.getGroups()) {
- logger.debugf("group %s", group.getName());
+ logger.debugf("group: %s", group.getName());
for (String policyString : group.getAttribute(groupAttribute)) {
logger.infof("adding group password policy: %s", policyString);
policyDefinitions.add(policyString);
@@ -52,16 +47,5 @@ public class GroupPasswordPolicyProvider extends PolicyProviderMultiplexer {
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() {
- }
+
}
diff --git a/src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java b/src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java
new file mode 100644
index 0000000..eee8b76
--- /dev/null
+++ b/src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java
@@ -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 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() {
+ }
+
+}
diff --git a/src/main/java/com/github/jpicht/keycloak/policy/PolicyProviderMultiplexer.java b/src/main/java/com/github/jpicht/keycloak/policy/PolicyProviderMultiplexer.java
index f6b199d..fa0bb8e 100644
--- a/src/main/java/com/github/jpicht/keycloak/policy/PolicyProviderMultiplexer.java
+++ b/src/main/java/com/github/jpicht/keycloak/policy/PolicyProviderMultiplexer.java
@@ -39,7 +39,7 @@ abstract public class PolicyProviderMultiplexer implements PasswordPolicyProvide
this.session = session;
}
- abstract protected LinkedList findPolicies(RealmModel realm, UserModel user);
+ protected abstract List findPolicies(RealmModel realm, UserModel user);
@Override
public PolicyError validate(String username, String password) {
diff --git a/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java b/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java
new file mode 100644
index 0000000..f412212
--- /dev/null
+++ b/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java
@@ -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);
+ }
+
+}