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