add ConditionalUserAttributeValue authenticator
This commit is contained in:
commit
b22d6fe2f7
|
@ -0,0 +1,3 @@
|
|||
/ca
|
||||
/target
|
||||
/META-INF
|
|
@ -0,0 +1,13 @@
|
|||
# keycloak-spi
|
||||
This repository contains some custom [Keycloak SPIs](https://www.keycloak.org/docs/latest/server_development/#_providers)
|
||||
used by CSC on our Keycloak instance.
|
||||
|
||||
## Build
|
||||
Requires OpenJDK 11+ and Maven 3.6+.
|
||||
```sh
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
## Install
|
||||
Copy target/csc-keycloak-spi.jar to /opt/jboss/keycloak/standalone/deployments
|
||||
in the container where Keycloak is running.
|
|
@ -0,0 +1,92 @@
|
|||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ca.uwaterloo.csclub</groupId>
|
||||
<artifactId>keycloak-spi</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<version.org.keycloak>16.1.0</version.org.keycloak>
|
||||
<version.org.jboss.logging>3.4.1.Final</version.org.jboss.logging>
|
||||
<version.org.apache.maven.plugins.maven-jar-plugin>3.2.0</version.org.apache.maven.plugins.maven-jar-plugin>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${version.org.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
<version>${version.org.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi-private</artifactId>
|
||||
<version>${version.org.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<version>${version.org.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${version.org.jboss.logging}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>csc-keycloak-spi</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<version>${version.org.keycloak}</version>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- see https://stackoverflow.com/a/57791073 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>${version.org.apache.maven.plugins.maven-jar-plugin}</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Dependencies>org.keycloak.keycloak-services</Dependencies>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,56 @@
|
|||
// Copied from https://github.com/keycloak/keycloak/blob/main/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/ConditionalUserAttributeValue.java
|
||||
package ca.uwaterloo.csclub.keycloakspi.authenticator;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.AuthenticationFlowException;
|
||||
import org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticator;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class ConditionalUserAttributeValue implements ConditionalAuthenticator {
|
||||
|
||||
static final ConditionalUserAttributeValue SINGLETON = new ConditionalUserAttributeValue();
|
||||
|
||||
@Override
|
||||
public boolean matchCondition(AuthenticationFlowContext context) {
|
||||
// Retrieve configuration
|
||||
Map<String, String> config = context.getAuthenticatorConfig().getConfig();
|
||||
String attributeName = config.get(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_NAME);
|
||||
String attributeValue = config.get(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_EXPECTED_VALUE);
|
||||
boolean negateOutput = Boolean.parseBoolean(config.get(ConditionalUserAttributeValueFactory.CONF_NOT));
|
||||
|
||||
UserModel user = context.getUser();
|
||||
if (user == null) {
|
||||
throw new AuthenticationFlowException("authenticator: " + ConditionalUserAttributeValueFactory.PROVIDER_ID, AuthenticationFlowError.UNKNOWN_USER);
|
||||
}
|
||||
|
||||
boolean result = user.getAttributeStream(attributeName).anyMatch(attr -> Objects.equals(attr, attributeValue));
|
||||
return negateOutput != result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
// Not used
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
// Not used
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Does nothing
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// Copied from https://github.com/keycloak/keycloak/blob/main/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/ConditionalUserAttributeValueFactory.java
|
||||
package ca.uwaterloo.csclub.keycloakspi.authenticator;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ConditionalUserAttributeValueFactory implements ConditionalAuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "conditional-user-attribute";
|
||||
|
||||
public static final String CONF_ATTRIBUTE_NAME = "attribute_name";
|
||||
public static final String CONF_ATTRIBUTE_EXPECTED_VALUE = "attribute_expected_value";
|
||||
public static final String CONF_NOT = "not";
|
||||
|
||||
private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED
|
||||
};
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Condition - user attribute";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return "condition";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Flow is executed only if the user attribute exists and has the expected value";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
ProviderConfigProperty authNoteName = new ProviderConfigProperty();
|
||||
authNoteName.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
authNoteName.setName(CONF_ATTRIBUTE_NAME);
|
||||
authNoteName.setLabel("Attribute name");
|
||||
authNoteName.setHelpText("Name of the attribute to check");
|
||||
|
||||
ProviderConfigProperty authNoteExpectedValue = new ProviderConfigProperty();
|
||||
authNoteExpectedValue.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
authNoteExpectedValue.setName(CONF_ATTRIBUTE_EXPECTED_VALUE);
|
||||
authNoteExpectedValue.setLabel("Expected attribute value");
|
||||
authNoteExpectedValue.setHelpText("Expected value in the attribute");
|
||||
|
||||
ProviderConfigProperty negateOutput = new ProviderConfigProperty();
|
||||
negateOutput.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||
negateOutput.setName(CONF_NOT);
|
||||
negateOutput.setLabel("Negate output");
|
||||
negateOutput.setHelpText("Apply a not to the check result");
|
||||
|
||||
return Arrays.asList(authNoteName, authNoteExpectedValue, negateOutput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionalAuthenticator getSingleton() {
|
||||
return ConditionalUserAttributeValue.SINGLETON;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
ca.uwaterloo.csclub.keycloakspi.authenticator.ConditionalUserAttributeValueFactory
|
Loading…
Reference in New Issue