diff --git a/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionFactory.java b/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionFactory.java new file mode 100644 index 0000000..4ab83cb --- /dev/null +++ b/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionFactory.java @@ -0,0 +1,68 @@ +/* + * 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 org.jboss.logging.Logger; +import org.keycloak.Config.Scope; +import org.keycloak.authentication.RequiredActionFactory; +import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +import com.google.auto.service.AutoService; + +/** + * @author brian@inteligr8.com + */ +@AutoService(RequiredActionFactory.class) +public class GroupRequiredActionFactory implements RequiredActionFactory { + + private final Logger logger = Logger.getLogger(GroupRequiredActionFactory.class); + + private static final String ID = "groupRequiredAction"; + private static final String DISPLAY = "Group Action"; + + @Override + public String getId() { + return ID; + } + + @Override + public RequiredActionProvider create(KeycloakSession session) { + return new GroupRequiredActionProvider(session); + } + + @Override + public void init(Scope config) { + this.logger.trace("init()"); + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public String getDisplayText() { + return DISPLAY; + } + + @Override + public void close() { + this.logger.trace("close()"); + } + +} diff --git a/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionProvider.java b/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionProvider.java new file mode 100644 index 0000000..28c9ca5 --- /dev/null +++ b/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionProvider.java @@ -0,0 +1,73 @@ +/* + * 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.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +/** + * @author brian@inteligr8.com + */ +public class GroupRequiredActionProvider extends RequiredActionMultiplexer { + + private final Logger logger = Logger.getLogger(RequiredActionMultiplexer.class); + private final GroupPasswordPolicyFinder finder = new GroupPasswordPolicyFinder(); + private final KeycloakSession session; + + public GroupRequiredActionProvider(KeycloakSession session) { + this.session = session; + } + + @Override + protected int findDaysToExpire(RealmModel realm, UserModel user) { + List policyStrs = this.finder.findPolicies(realm, user); + if (policyStrs == null || policyStrs.isEmpty()) + return -1; + + Integer minDaysToExpire = null; + + for (String policyStr : policyStrs) { + PasswordPolicy policy = PasswordPolicy.parse(this.session, policyStr); + int daysToExpire = policy.getDaysToExpirePassword(); + if (daysToExpire < 0) + // policy does not have an expiration characteristic; so ignoring ... + continue; + + this.logger.debugf("found password expiration policy: %d days", daysToExpire); + + if (minDaysToExpire == null) { + // days to expire was set + minDaysToExpire = daysToExpire; + } else { + // days to expire was set; we want the most restrictive + minDaysToExpire = Math.min(minDaysToExpire, daysToExpire); + } + } + + return minDaysToExpire == null ? -1 : minDaysToExpire; + } + + @Override + public void close() { + } + +} diff --git a/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java b/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java index f412212..3584fea 100644 --- a/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java +++ b/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java @@ -47,7 +47,7 @@ abstract class RequiredActionMultiplexer implements RequiredActionProvider { */ @Override public void evaluateTriggers(RequiredActionContext context) { - this.logger.tracef("evaluateTriggers({})", context.getUser() != null ? context.getUser().getUsername() : null); + this.logger.tracef("evaluateTriggers(%s)", context.getUser() != null ? context.getUser().getUsername() : null); int daysToExpirePassword = this.findDaysToExpire(context.getRealm(), context.getUser()); if (daysToExpirePassword > -1) { @@ -73,12 +73,12 @@ abstract class RequiredActionMultiplexer implements RequiredActionProvider { @Override public void requiredActionChallenge(RequiredActionContext context) { - this.logger.tracef("requiredActionChallenge({})", context.getUser() != null ? context.getUser().getUsername() : null); + this.logger.tracef("requiredActionChallenge(%s)", context.getUser() != null ? context.getUser().getUsername() : null); } @Override public void processAction(RequiredActionContext context) { - this.logger.tracef("processAction({})", context.getUser() != null ? context.getUser().getUsername() : null); + this.logger.tracef("processAction(%s)", context.getUser() != null ? context.getUser().getUsername() : null); } }