From 919c288ba93c89ca91b05d4c7d1fda81639c2612 Mon Sep 17 00:00:00 2001 From: Brian Long Date: Thu, 21 Oct 2021 15:24:18 -0400 Subject: [PATCH 1/6] added support for builds against keycloak v6-v15 --- .gitignore | 11 + pom.xml | 346 +++++++++++++++++- .../jpicht/keycloak/policy/FakeRealmV10.java | 41 +++ .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../jpicht/keycloak/policy/FakeRealmV11.java | 41 +++ .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../jpicht/keycloak/policy/FakeRealmV12.java | 202 ++++++++++ .../policy/GroupPasswordPolicyProvider.java | 75 ++++ .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../jpicht/keycloak/policy/FakeRealmV13.java | 73 ++++ .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../jpicht/keycloak/policy/FakeRealmV14.java | 31 ++ .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../jpicht/keycloak/policy/FakeRealmV15.java | 28 ++ .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../jpicht/keycloak/policy/FakeRealmV6.java} | 10 +- .../policy/GroupPasswordPolicyProvider.java | 0 .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../jpicht/keycloak/policy/FakeRealmV8.java | 39 ++ .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../jpicht/keycloak/policy/FakeRealmV9.java | 74 ++++ .../jpicht/keycloak/policy/FakeRealm.java | 36 ++ .../policy/PolicyProviderMultiplexer.java | 2 +- 23 files changed, 1271 insertions(+), 26 deletions(-) create mode 100644 src/keycloak-v10-common/java/com/github/jpicht/keycloak/policy/FakeRealmV10.java create mode 100644 src/keycloak-v10/java/com/github/jpicht/keycloak/policy/FakeRealm.java create mode 100644 src/keycloak-v11-common/java/com/github/jpicht/keycloak/policy/FakeRealmV11.java create mode 100644 src/keycloak-v11/java/com/github/jpicht/keycloak/policy/FakeRealm.java create mode 100644 src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/FakeRealmV12.java create mode 100644 src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java create mode 100644 src/keycloak-v12/java/com/github/jpicht/keycloak/policy/FakeRealm.java create mode 100644 src/keycloak-v13-common/java/com/github/jpicht/keycloak/policy/FakeRealmV13.java create mode 100644 src/keycloak-v13/java/com/github/jpicht/keycloak/policy/FakeRealm.java create mode 100644 src/keycloak-v14-common/java/com/github/jpicht/keycloak/policy/FakeRealmV14.java create mode 100644 src/keycloak-v14/java/com/github/jpicht/keycloak/policy/FakeRealm.java create mode 100644 src/keycloak-v15-common/java/com/github/jpicht/keycloak/policy/FakeRealmV15.java create mode 100644 src/keycloak-v15/java/com/github/jpicht/keycloak/policy/FakeRealm.java rename src/{main/java/com/github/jpicht/keycloak/policy/FakeRealm.java => keycloak-v6-common/java/com/github/jpicht/keycloak/policy/FakeRealmV6.java} (99%) rename src/{main => keycloak-v6-v11}/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java (100%) create mode 100644 src/keycloak-v6/java/com/github/jpicht/keycloak/policy/FakeRealm.java create mode 100644 src/keycloak-v8-common/java/com/github/jpicht/keycloak/policy/FakeRealmV8.java create mode 100644 src/keycloak-v8/java/com/github/jpicht/keycloak/policy/FakeRealm.java create mode 100644 src/keycloak-v9-common/java/com/github/jpicht/keycloak/policy/FakeRealmV9.java create mode 100644 src/keycloak-v9/java/com/github/jpicht/keycloak/policy/FakeRealm.java diff --git a/.gitignore b/.gitignore index eb5a316..683f09c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,12 @@ +# Maven target +pom.xml.versionsBackup + +# Eclipse +.project +.classpath +.settings + +# VS Code +.factorypath +.vscode diff --git a/pom.xml b/pom.xml index 47b7e07..72f52ba 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.github.jpicht.keycloak.policy keycloak-group-password-policy - 0.1-SNAPSHOT + 0.2-SNAPSHOT jar @@ -13,8 +13,8 @@ ${java.version} ${java.version} - 6.0.1 - 1.0-rc5 + ${keycloak.majorVersion}.0.0 + 1.0 @@ -45,23 +45,6 @@ provided true - - org.keycloak.testsuite - integration-arquillian-tests-base - 6.0.1 - test - - - org.keycloak - keycloak-test-helper - 6.0.1 - test - - - org.keycloak - keycloak-services - 6.0.1 - @@ -71,7 +54,330 @@ maven-jar-plugin 3.1.1 + + org.codehaus.mojo + build-helper-maven-plugin + 3.2.0 + + + + + org.apache.maven.plugins + maven-jar-plugin + + keycloak-v${keycloak.majorVersion} + + + + + + + keycloak-v6 + + 6 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v6 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v6-v11/java + ${basedir}/src/keycloak-v6/java + + + + + + + + + + keycloak-v7 + + true + + + 7 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v7 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v6-v11/java + ${basedir}/src/keycloak-v6/java + + + + + + + + + + keycloak-v8 + + 8 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v8 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v8-common/java + ${basedir}/src/keycloak-v6-v11/java + ${basedir}/src/keycloak-v8/java + + + + + + + + + + keycloak-v9 + + 9 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v9 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v8-common/java + ${basedir}/src/keycloak-v9-common/java + ${basedir}/src/keycloak-v6-v11/java + ${basedir}/src/keycloak-v9/java + + + + + + + + + + keycloak-v10 + + 10 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v10 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v8-common/java + ${basedir}/src/keycloak-v9-common/java + ${basedir}/src/keycloak-v10-common/java + ${basedir}/src/keycloak-v6-v11/java + ${basedir}/src/keycloak-v10/java + + + + + + + + + + keycloak-v11 + + 11 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v11 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v8-common/java + ${basedir}/src/keycloak-v9-common/java + ${basedir}/src/keycloak-v10-common/java + ${basedir}/src/keycloak-v11-common/java + ${basedir}/src/keycloak-v6-v11/java + ${basedir}/src/keycloak-v11/java + + + + + + + + + + keycloak-v12 + + 12 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v12 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v8-common/java + ${basedir}/src/keycloak-v9-common/java + ${basedir}/src/keycloak-v10-common/java + ${basedir}/src/keycloak-v11-common/java + ${basedir}/src/keycloak-v12-common/java + ${basedir}/src/keycloak-v12/java + + + + + + + + + + keycloak-v13 + + 13 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v13 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v8-common/java + ${basedir}/src/keycloak-v9-common/java + ${basedir}/src/keycloak-v10-common/java + ${basedir}/src/keycloak-v11-common/java + ${basedir}/src/keycloak-v12-common/java + ${basedir}/src/keycloak-v13-common/java + ${basedir}/src/keycloak-v13/java + + + + + + + + + + keycloak-v14 + + 14 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v14 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v8-common/java + ${basedir}/src/keycloak-v9-common/java + ${basedir}/src/keycloak-v10-common/java + ${basedir}/src/keycloak-v11-common/java + ${basedir}/src/keycloak-v12-common/java + ${basedir}/src/keycloak-v13-common/java + ${basedir}/src/keycloak-v14-common/java + ${basedir}/src/keycloak-v14/java + + + + + + + + + + keycloak-v15 + + 15 + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source-keycloak-v15 + add-source + + + ${basedir}/src/keycloak-v6-common/java + ${basedir}/src/keycloak-v8-common/java + ${basedir}/src/keycloak-v9-common/java + ${basedir}/src/keycloak-v10-common/java + ${basedir}/src/keycloak-v11-common/java + ${basedir}/src/keycloak-v12-common/java + ${basedir}/src/keycloak-v13-common/java + ${basedir}/src/keycloak-v14-common/java + ${basedir}/src/keycloak-v15-common/java + ${basedir}/src/keycloak-v15/java + + + + + + + + + diff --git a/src/keycloak-v10-common/java/com/github/jpicht/keycloak/policy/FakeRealmV10.java b/src/keycloak-v10-common/java/com/github/jpicht/keycloak/policy/FakeRealmV10.java new file mode 100644 index 0000000..11fee0a --- /dev/null +++ b/src/keycloak-v10-common/java/com/github/jpicht/keycloak/policy/FakeRealmV10.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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; + +public abstract class FakeRealmV10 extends FakeRealmV9 { + + @Override + public int getClientSessionIdleTimeout() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setClientSessionIdleTimeout(int seconds) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getClientSessionMaxLifespan() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setClientSessionMaxLifespan(int seconds) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/keycloak-v10/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v10/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..7d79c71 --- /dev/null +++ b/src/keycloak-v10/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV10 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} diff --git a/src/keycloak-v11-common/java/com/github/jpicht/keycloak/policy/FakeRealmV11.java b/src/keycloak-v11-common/java/com/github/jpicht/keycloak/policy/FakeRealmV11.java new file mode 100644 index 0000000..ed6de27 --- /dev/null +++ b/src/keycloak-v11-common/java/com/github/jpicht/keycloak/policy/FakeRealmV11.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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; + +public abstract class FakeRealmV11 extends FakeRealmV10 { + + @Override + public int getClientOfflineSessionIdleTimeout() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setClientOfflineSessionIdleTimeout(int seconds) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getClientOfflineSessionMaxLifespan() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setClientOfflineSessionMaxLifespan(int seconds) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/keycloak-v11/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v11/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..6ad6c88 --- /dev/null +++ b/src/keycloak-v11/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV11 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} diff --git a/src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/FakeRealmV12.java b/src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/FakeRealmV12.java new file mode 100644 index 0000000..d3960e2 --- /dev/null +++ b/src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/FakeRealmV12.java @@ -0,0 +1,202 @@ +/* + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.Map; +import java.util.stream.Stream; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.AuthenticationFlowModel; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientScopeModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.RequiredActionProviderModel; +import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.models.RoleModel; + +public abstract class FakeRealmV12 extends FakeRealmV11 { + + @Override + public Stream getRequiredCredentialsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getDefaultGroupsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getClientsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getClientsStream(Integer firstResult, Integer maxResults) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getAlwaysDisplayInConsoleClientsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream searchClientByClientIdStream(String clientId, Integer firstResult, Integer maxResults) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getAuthenticationFlowsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getAuthenticationExecutionsStream(String flowId) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getAuthenticatorConfigsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getRequiredActionProvidersStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getIdentityProvidersStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getIdentityProviderMappersStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getIdentityProviderMappersByAliasStream(String brokerAlias) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getComponentsStream(String parentId, String providerType) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getComponentsStream(String parentId) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getComponentsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getEventsListenersStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getEnabledEventTypesStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getSupportedLocalesStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getGroupsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getTopLevelGroupsStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getTopLevelGroupsStream(Integer first, Integer max) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream searchForGroupByNameStream(String search, Integer first, Integer max) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getClientScopesStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void patchRealmLocalizationTexts(String locale, Map localizationTexts) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean removeRealmLocalizationTexts(String locale) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Map> getRealmLocalizationTexts() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Map getRealmLocalizationTextsByLocale(String locale) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getDefaultClientScopesStream(boolean defaultScope) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getRolesStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getRolesStream(Integer firstResult, Integer maxResults) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream searchForRolesStream(String search, Integer first, Integer max) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getDefaultRolesStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + +} 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/GroupPasswordPolicyProvider.java new file mode 100644 index 0000000..bc42684 --- /dev/null +++ b/src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Julian Picht + * + * 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.LinkedList; +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 { + + private static final Logger logger = Logger.getLogger(GroupPasswordPolicyProvider.class); + + public GroupPasswordPolicyProvider(KeycloakSession session) { + super(session); + } + + @Override + protected LinkedList 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()); + + LinkedList policyDefinitions = new LinkedList<>(); + + // Iterate groups and collect policy strings + user.getGroupsStream().forEach(new Consumer() { + @Override + public void accept(GroupModel group) { + logger.debugf("group %s", group.getName()); + group.getAttributeStream(groupAttribute).forEach(new Consumer() { + @Override + public void accept(String policyString) { + logger.infof("adding group password policy: %s", policyString); + policyDefinitions.add(policyString); + } + }); + } + }); + + 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-v12/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v12/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..e450f75 --- /dev/null +++ b/src/keycloak-v12/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV12 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} diff --git a/src/keycloak-v13-common/java/com/github/jpicht/keycloak/policy/FakeRealmV13.java b/src/keycloak-v13-common/java/com/github/jpicht/keycloak/policy/FakeRealmV13.java new file mode 100644 index 0000000..db28517 --- /dev/null +++ b/src/keycloak-v13-common/java/com/github/jpicht/keycloak/policy/FakeRealmV13.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.stream.Stream; + +import org.keycloak.models.CibaConfig; +import org.keycloak.models.ClientInitialAccessModel; +import org.keycloak.models.OAuth2DeviceConfig; +import org.keycloak.models.RoleModel; + +public abstract class FakeRealmV13 extends FakeRealmV12 { + + @Override + public OAuth2DeviceConfig getOAuth2DeviceConfig() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CibaConfig getCibaPolicy() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public RoleModel getDefaultRole() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setDefaultRole(RoleModel role) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ClientInitialAccessModel createClientInitialAccessModel(int expiration, int count) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ClientInitialAccessModel getClientInitialAccessModel(String id) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void removeClientInitialAccessModel(String id) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getClientInitialAccesses() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void decreaseRemainingCount(ClientInitialAccessModel clientInitialAccess) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/keycloak-v13/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v13/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..cdef8d4 --- /dev/null +++ b/src/keycloak-v13/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV13 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} diff --git a/src/keycloak-v14-common/java/com/github/jpicht/keycloak/policy/FakeRealmV14.java b/src/keycloak-v14-common/java/com/github/jpicht/keycloak/policy/FakeRealmV14.java new file mode 100644 index 0000000..092f7e9 --- /dev/null +++ b/src/keycloak-v14-common/java/com/github/jpicht/keycloak/policy/FakeRealmV14.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.Map; +import java.util.stream.Stream; + +import org.keycloak.models.ClientModel; + +public abstract class FakeRealmV14 extends FakeRealmV13 { + + @Override + public Stream searchClientByAttributes(Map attributes, Integer firstResult, Integer maxResults) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/keycloak-v14/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v14/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..f482c08 --- /dev/null +++ b/src/keycloak-v14/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV14 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} diff --git a/src/keycloak-v15-common/java/com/github/jpicht/keycloak/policy/FakeRealmV15.java b/src/keycloak-v15-common/java/com/github/jpicht/keycloak/policy/FakeRealmV15.java new file mode 100644 index 0000000..9197a5a --- /dev/null +++ b/src/keycloak-v15-common/java/com/github/jpicht/keycloak/policy/FakeRealmV15.java @@ -0,0 +1,28 @@ +/* + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.ParConfig; + +public class FakeRealmV15 extends FakeRealmV14 { + + @Override + public ParConfig getParPolicy() { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/keycloak-v15/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v15/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..d335bf9 --- /dev/null +++ b/src/keycloak-v15/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV15 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} diff --git a/src/main/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v6-common/java/com/github/jpicht/keycloak/policy/FakeRealmV6.java similarity index 99% rename from src/main/java/com/github/jpicht/keycloak/policy/FakeRealm.java rename to src/keycloak-v6-common/java/com/github/jpicht/keycloak/policy/FakeRealmV6.java index 1fd552a..6d92de7 100644 --- a/src/main/java/com/github/jpicht/keycloak/policy/FakeRealm.java +++ b/src/keycloak-v6-common/java/com/github/jpicht/keycloak/policy/FakeRealmV6.java @@ -1,5 +1,6 @@ /* * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,8 +37,8 @@ import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; -public class FakeRealm implements RealmModel { - +public abstract class FakeRealmV6 implements RealmModel { + @Override public String getId() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. @@ -508,15 +509,14 @@ public class FakeRealm implements RealmModel { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } - private PasswordPolicy passwordPolicy; @Override public PasswordPolicy getPasswordPolicy() { - return passwordPolicy; + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void setPasswordPolicy(PasswordPolicy policy) { - passwordPolicy = policy; + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override diff --git a/src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java b/src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java similarity index 100% rename from src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java rename to src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java diff --git a/src/keycloak-v6/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v6/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..efcb777 --- /dev/null +++ b/src/keycloak-v6/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV6 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} diff --git a/src/keycloak-v8-common/java/com/github/jpicht/keycloak/policy/FakeRealmV8.java b/src/keycloak-v8-common/java/com/github/jpicht/keycloak/policy/FakeRealmV8.java new file mode 100644 index 0000000..0736533 --- /dev/null +++ b/src/keycloak-v8-common/java/com/github/jpicht/keycloak/policy/FakeRealmV8.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.WebAuthnPolicy; + +public abstract class FakeRealmV8 extends FakeRealmV6 { + + @Override + public WebAuthnPolicy getWebAuthnPolicy() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setWebAuthnPolicy(WebAuthnPolicy policy) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public AuthenticationExecutionModel getAuthenticationExecutionByFlowId(String flowId) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/keycloak-v8/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v8/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..edb6df1 --- /dev/null +++ b/src/keycloak-v8/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV8 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} diff --git a/src/keycloak-v9-common/java/com/github/jpicht/keycloak/policy/FakeRealmV9.java b/src/keycloak-v9-common/java/com/github/jpicht/keycloak/policy/FakeRealmV9.java new file mode 100644 index 0000000..55919d5 --- /dev/null +++ b/src/keycloak-v9-common/java/com/github/jpicht/keycloak/policy/FakeRealmV9.java @@ -0,0 +1,74 @@ +/* + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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 java.util.Set; + +import org.keycloak.models.ClientModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.WebAuthnPolicy; + +public abstract class FakeRealmV9 extends FakeRealmV8 { + + @Override + public WebAuthnPolicy getWebAuthnPolicyPasswordless() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setWebAuthnPolicyPasswordless(WebAuthnPolicy policy) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getClients(Integer firstResult, Integer maxResults) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Long getClientsCount() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getAlwaysDisplayInConsoleClients() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List searchClientByClientId(String clientId, Integer firstResult, Integer maxResults) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public GroupModel createGroup(String id, String name, GroupModel toParent) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Set getRoles(Integer firstResult, Integer maxResults) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Set searchForRoles(String search, Integer first, Integer max) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/keycloak-v9/java/com/github/jpicht/keycloak/policy/FakeRealm.java b/src/keycloak-v9/java/com/github/jpicht/keycloak/policy/FakeRealm.java new file mode 100644 index 0000000..7fcc17a --- /dev/null +++ b/src/keycloak-v9/java/com/github/jpicht/keycloak/policy/FakeRealm.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Julian Picht + * Copyright 2021 Brian Long (brian@inteligr8.com) + * + * 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.keycloak.models.PasswordPolicy; + +public class FakeRealm extends FakeRealmV9 { + + private PasswordPolicy passwordPolicy; + + @Override + public PasswordPolicy getPasswordPolicy() { + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + passwordPolicy = policy; + } + +} 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 00a245c..f6b199d 100644 --- a/src/main/java/com/github/jpicht/keycloak/policy/PolicyProviderMultiplexer.java +++ b/src/main/java/com/github/jpicht/keycloak/policy/PolicyProviderMultiplexer.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Properties; + import org.keycloak.models.KeycloakSession; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; @@ -65,7 +66,6 @@ abstract public class PolicyProviderMultiplexer implements PasswordPolicyProvide // use org.keycloak.models.PasswordPolicy to parse the policy string protected PasswordPolicy parsePolicy(String policy) { - LinkedList list = new LinkedList<>(); PasswordPolicy parsedPolicy = PasswordPolicy.parse(session, policy); return parsedPolicy; } From ba887619e805a70a647a4162b182bf3d60225ea8 Mon Sep 17 00:00:00 2001 From: Brian Long Date: Thu, 21 Oct 2021 15:24:42 -0400 Subject: [PATCH 2/6] added more documentation --- README.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9f4b774..943660b 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,24 @@ The extension can be installed just like any keycloak extension. Either copy it `keycloak/standalone/deployments` folder, or load it via the jboss command line tool. ## Usage -To use the plugin you create a new password policy entry on the realm's password policy sub-page -with the `Group Policy` type, then enter a group attribute name as the configuration. -On a password change request, the extension will then check all the user's groups for this -attribute name and parse the corresponding attribute value as a serialized password policy. +There are multiple steps you will want to take to use this plugin. First, you need to determine +what password policies you will want for all users and for each group of users. Once you have +that, you will need to come up with an ID where you will specify group password policies. For +the purposes of this documentation we will use the ID `passwordPolicy`. + +Go to the realm's password policy page. In the latest versions of Keycloak, this can be found +by navigating to the "Authentication" menu item in the vertical menu on the left side of 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. + +This interface provides you the OOTB ability to specify password policies for all users. This +is still true with the plugin installed. You will also have an additional option: **Group +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`. + +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 +documentation and covered in the section below. ### Password policy format All policies are represented by a short string immediately followed by parenthesis, optionally @@ -46,11 +60,10 @@ If these currently work is completely untested. | Identifier | Description | Tested | | ------------- |:------------------------------------ | ------ | -| `forceExpiredPasswordChange(int)` | number of days to expire password after | - | +| `forceExpiredPasswordChange(string)` | number of days to expire password after | - | | `hashAlgorithm(string)` | hash algorithm to use when hashing the password | - | | `hashIterations(int)` | number of hash iterations | - | - ## Implementation To minimize code duplication the extension uses as much of the built-in KeyCloak code as possible. The parsing and instantiation of the policy provider classes is used as-is. From b50939bd1f59792b3f663ae4118091f56fd4db65 Mon Sep 17 00:00:00 2001 From: Brian Long Date: Mon, 1 Nov 2021 11:34:21 -0400 Subject: [PATCH 3/6] refactored to support required action multiplexing/reusability --- pom.xml | 5 ++ ...er.java => GroupPasswordPolicyFinder.java} | 34 ++------ ...er.java => GroupPasswordPolicyFinder.java} | 34 ++------ .../policy/GroupPasswordPolicyProvider.java | 52 ++++++++++++ .../policy/PolicyProviderMultiplexer.java | 2 +- .../policy/RequiredActionMultiplexer.java | 84 +++++++++++++++++++ 6 files changed, 160 insertions(+), 51 deletions(-) rename src/keycloak-v12-common/java/com/github/jpicht/keycloak/policy/{GroupPasswordPolicyProvider.java => GroupPasswordPolicyFinder.java} (67%) rename src/keycloak-v6-v11/java/com/github/jpicht/keycloak/policy/{GroupPasswordPolicyProvider.java => GroupPasswordPolicyFinder.java} (63%) create mode 100644 src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProvider.java create mode 100644 src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java 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); + } + +} From 05d12fd94fd7cb928aa9c9e3e949cd2ebdbcdd22 Mon Sep 17 00:00:00 2001 From: Brian Long Date: Mon, 1 Nov 2021 11:44:18 -0400 Subject: [PATCH 4/6] initial group required action impl (password expiration) --- .../policy/GroupRequiredActionFactory.java | 68 +++++++++++++++++ .../policy/GroupRequiredActionProvider.java | 73 +++++++++++++++++++ .../policy/RequiredActionMultiplexer.java | 6 +- 3 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionFactory.java create mode 100644 src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionProvider.java 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); } } From 7028aa55dc96b7129692121974d564f68ab67ac8 Mon Sep 17 00:00:00 2001 From: Brian Long Date: Tue, 2 Nov 2021 10:55:20 -0400 Subject: [PATCH 5/6] fixes/refactoring after testing --- README.md | 14 ++++++++++---- pom.xml | 6 ++++++ ...oupExpiredPasswordRequiredActionFactory.java} | 9 +++++---- ...upExpiredPasswordRequiredActionProvider.java} | 13 ++++++++++--- .../GroupPasswordPolicyProviderFactory.java | 6 ++++++ .../policy/RequiredActionMultiplexer.java | 16 ++++++++++++++-- 6 files changed, 51 insertions(+), 13 deletions(-) rename src/main/java/com/github/jpicht/keycloak/policy/{GroupRequiredActionFactory.java => GroupExpiredPasswordRequiredActionFactory.java} (81%) rename src/main/java/com/github/jpicht/keycloak/policy/{GroupRequiredActionProvider.java => GroupExpiredPasswordRequiredActionProvider.java} (79%) diff --git a/README.md b/README.md index 943660b..b435012 100644 --- a/README.md +++ b/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 menu of tabs on the top of the page. -This interface provides you the OOTB ability to specify password policies for all users. This -is still true with the plugin installed. You will also have an additional option: **Group +This interface provides you the OOTB ability to specify password policies for **all** users. +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 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 want to have additional password policies. The format of that text is defined by Keycloak 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 | ✓ | | `notUsername()` | | ✓ | | `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. 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 | | ------------- |:------------------------------------ | ------ | -| `forceExpiredPasswordChange(string)` | number of days to expire password after | - | | `hashAlgorithm(string)` | hash algorithm to use when hashing the password | - | | `hashIterations(int)` | number of hash iterations | - | diff --git a/pom.xml b/pom.xml index 163d564..a46bf3b 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,12 @@ maven-jar-plugin keycloak-v${keycloak.majorVersion} + + + + org.keycloak.keycloak-services + + diff --git a/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionFactory.java b/src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionFactory.java similarity index 81% rename from src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionFactory.java rename to src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionFactory.java index 4ab83cb..baaccc7 100644 --- a/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionFactory.java +++ b/src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionFactory.java @@ -29,12 +29,12 @@ import com.google.auto.service.AutoService; * @author brian@inteligr8.com */ @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 DISPLAY = "Group Action"; + private static final String DISPLAY = "Group-based Expired Password"; @Override public String getId() { @@ -43,7 +43,8 @@ public class GroupRequiredActionFactory implements RequiredActionFactory { @Override public RequiredActionProvider create(KeycloakSession session) { - return new GroupRequiredActionProvider(session); + this.logger.trace("create()"); + return new GroupExpiredPasswordRequiredActionProvider(session); } @Override diff --git a/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionProvider.java b/src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionProvider.java similarity index 79% rename from src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionProvider.java rename to src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionProvider.java index 28c9ca5..350e28c 100644 --- a/src/main/java/com/github/jpicht/keycloak/policy/GroupRequiredActionProvider.java +++ b/src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionProvider.java @@ -27,25 +27,31 @@ import org.keycloak.models.UserModel; /** * @author brian@inteligr8.com */ -public class GroupRequiredActionProvider extends RequiredActionMultiplexer { +public class GroupExpiredPasswordRequiredActionProvider extends RequiredActionMultiplexer { private final Logger logger = Logger.getLogger(RequiredActionMultiplexer.class); private final GroupPasswordPolicyFinder finder = new GroupPasswordPolicyFinder(); private final KeycloakSession session; - public GroupRequiredActionProvider(KeycloakSession session) { + public GroupExpiredPasswordRequiredActionProvider(KeycloakSession session) { this.session = session; } @Override 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 policyStrs = this.finder.findPolicies(realm, user); if (policyStrs == null || policyStrs.isEmpty()) return -1; + this.logger.debugf("found policies: [%s]", policyStrs.toString()); Integer minDaysToExpire = null; for (String policyStr : policyStrs) { + this.logger.tracef("inspecting policy: %s", policyStr); + PasswordPolicy policy = PasswordPolicy.parse(this.session, policyStr); int daysToExpire = policy.getDaysToExpirePassword(); if (daysToExpire < 0) @@ -62,7 +68,8 @@ public class GroupRequiredActionProvider extends RequiredActionMultiplexer { minDaysToExpire = Math.min(minDaysToExpire, daysToExpire); } } - + + this.logger.debugf("determined password expiration policy: %d days", minDaysToExpire); return minDaysToExpire == null ? -1 : minDaysToExpire; } diff --git a/src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProviderFactory.java b/src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProviderFactory.java index 1ff1792..abaff5d 100644 --- a/src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProviderFactory.java +++ b/src/main/java/com/github/jpicht/keycloak/policy/GroupPasswordPolicyProviderFactory.java @@ -17,6 +17,8 @@ package com.github.jpicht.keycloak.policy; import com.google.auto.service.AutoService; + +import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -25,6 +27,8 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; @AutoService(PasswordPolicyProviderFactory.class) public class GroupPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory { + + private final Logger logger = Logger.getLogger(GroupPasswordPolicyProviderFactory.class); static final String ID = "groupPasswordPolicy"; @@ -40,6 +44,7 @@ public class GroupPasswordPolicyProviderFactory implements PasswordPolicyProvide @Override public void init(Config.Scope config) { + this.logger.trace("init()"); } @Override @@ -68,5 +73,6 @@ public class GroupPasswordPolicyProviderFactory implements PasswordPolicyProvide @Override public void close() { + this.logger.trace("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 3584fea..ac82f5b 100644 --- a/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java +++ b/src/main/java/com/github/jpicht/keycloak/policy/RequiredActionMultiplexer.java @@ -19,6 +19,7 @@ package com.github.jpicht.keycloak.policy; import java.util.concurrent.TimeUnit; import org.jboss.logging.Logger; +import org.keycloak.authentication.InitiatedActionSupport; import org.keycloak.authentication.RequiredActionContext; import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.common.util.Time; @@ -37,6 +38,11 @@ abstract class RequiredActionMultiplexer implements RequiredActionProvider { private final Logger logger = Logger.getLogger(RequiredActionMultiplexer.class); 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 @@ -51,20 +57,26 @@ abstract class RequiredActionMultiplexer implements RequiredActionProvider { int daysToExpirePassword = this.findDaysToExpire(context.getRealm(), context.getUser()); if (daysToExpirePassword > -1) { + this.logger.debugf("Found password expiration: %d days", daysToExpirePassword); + PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider)context.getSession() .getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID); CredentialModel password = passwordProvider.getPassword(context.getRealm(), context.getUser()); if (password != null) { + this.logger.tracef("Found password credentials; created: %d ms", password.getCreatedDate()); + if(password.getCreatedDate() == null) { 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 { 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"); + this.logger.debug("User is required to update password"); + } else { + this.logger.tracef("Password credentials expire in %d ms", timeToExpire); } } } From 6e9de6e465b7c877f4e54260f1a0afafd7afb8c3 Mon Sep 17 00:00:00 2001 From: Brian Long Date: Tue, 2 Nov 2021 10:57:33 -0400 Subject: [PATCH 6/6] set better action ID --- .../policy/GroupExpiredPasswordRequiredActionFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionFactory.java b/src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionFactory.java index baaccc7..1cf455a 100644 --- a/src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionFactory.java +++ b/src/main/java/com/github/jpicht/keycloak/policy/GroupExpiredPasswordRequiredActionFactory.java @@ -33,7 +33,7 @@ public class GroupExpiredPasswordRequiredActionFactory implements RequiredAction private final Logger logger = Logger.getLogger(GroupExpiredPasswordRequiredActionFactory.class); - private static final String ID = "groupRequiredAction"; + private static final String ID = "groupExpirePassswordAction"; private static final String DISPLAY = "Group-based Expired Password"; @Override