add ConditionalUserAttributeValue authenticator

master
Max Erenberg 9 months ago
commit b22d6fe2f7
  1. 3
      .gitignore
  2. 13
      README.md
  3. 92
      pom.xml
  4. 56
      src/main/java/ca/uwaterloo/csclub/keycloakspi/authenticator/ConditionalUserAttributeValue.java
  5. 103
      src/main/java/ca/uwaterloo/csclub/keycloakspi/authenticator/ConditionalUserAttributeValueFactory.java
  6. 1
      src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory

3
.gitignore vendored

@ -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…
Cancel
Save