-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new authenticator required action (#16)
* Fix 'NoClassDefFoundError: org/apache/logging/log4j/util/Lazy' error * Fix 'HTTP 405 Method Not Allowed' error when calling the REST endpoint * Add JUnit test for event listener * Add resources * Edit JUnit-test to extract expected successfull login time from access-token instead of logs * Remove unnecessary environment variable configuration * Refactor JUnit test: Extract from access-token without adding new external dependencies. * Remove unnecessary dependency * Refactor: Change access modifiers of methods to private * Refactor JUnit test: Remove duplicate method * Rename test files to *IT.java and configure Maven Failsafe plugin * Fixing open conversations on PR, refactored MAVEN-Plugins * WiP added a functional Authenticator without any testing. Tests will be added next * WiP added UI testing for Terms and condition * Added UI_Tests to check functionality for required action authenticator * change package to verify * add steps for playwright tests * add steps for playwright tests #2 * added documentation * fix missing dash * add one more header * fixing comments * WiP added check for empty AuthenticatorConfig * WiP added check for empty AuthenticatorConfig #2 added TODO to test * WiP added check for empty AuthenticatorConfig #2 added TODO to test * Resolved all comments --------- Co-authored-by: Hassan El Mailoudi <[email protected]>
- Loading branch information
Showing
16 changed files
with
2,416 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Authenticator-Required-Action | ||
Imagine a user authenticates via an Identity Provider (IdP) and requires a specific action, such as resetting their password or agreeing to new terms of service. | ||
This extension ensures that the configured required action is automatically set for the user once they authenticate through the Identity Provider. | ||
```mermaid | ||
flowchart LR | ||
CompanyA(RealmCompanyA)-->UsersA | ||
CompanyB(RealmCompanyB)-->UsersB | ||
UsersA-- Connected via Idp alias: CompanyA ---RealmConciso | ||
UsersB-- Connected via Idp alias: CompanyB ---RealmConciso | ||
``` | ||
|
||
The First Broker login of Idp CompanyA is configured to set the 'TERMS_AND_CONDITIONS' for all users from CompanyA. Users from CompanyB dont have to accept the terms, so they dont get that action. | ||
|
||
## How to use | ||
![required-action-flow.png](../docs/pics/required-action-flow.png) | ||
|
||
![required-action-authenticator-config.png](../docs/pics/required-action-authenticator-config.png) | ||
|
||
To see your available Required-Actions, go to Realm 'master' -> Provider info -> Search for 'req' or scroll down until you see 'required-action' in the column for SPI | ||
![required-action-available-required-actions.png](../docs/pics/required-action-available-required-actions.png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<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/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>de.conciso.keycloak-extensions</groupId> | ||
<artifactId>parent</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>authenticator-required-action</artifactId> | ||
|
||
<properties> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
|
||
<dependencies> | ||
<!-- Keycloak Extension --> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-core</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-server-spi</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-server-spi-private</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-services</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.microsoft.playwright</groupId> | ||
<artifactId>playwright</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-failsafe-plugin</artifactId> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
95 changes: 95 additions & 0 deletions
95
.../java/de/conciso/keycloak/authentication/required_action/RequiredActionAuthenticator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package de.conciso.keycloak.authentication.required_action; | ||
|
||
import jakarta.ws.rs.core.Response; | ||
import org.jboss.logging.Logger; | ||
import org.keycloak.authentication.AuthenticationFlowContext; | ||
import org.keycloak.authentication.AuthenticationFlowError; | ||
import org.keycloak.authentication.Authenticator; | ||
import org.keycloak.events.Errors; | ||
import org.keycloak.models.KeycloakSession; | ||
import org.keycloak.models.RealmModel; | ||
import org.keycloak.models.RequiredActionProviderModel; | ||
import org.keycloak.models.UserModel; | ||
import org.keycloak.sessions.AuthenticationSessionModel; | ||
|
||
import java.util.Optional; | ||
|
||
public class RequiredActionAuthenticator implements Authenticator { | ||
private static final Logger LOGGER = Logger.getLogger(RequiredActionAuthenticator.class); | ||
private static final String LOG_ERROR_MESSAGE_MISSING_AUTH_CONFIG = "AuthenticatorConfig is missing on RequiredActionAuthenticator"; | ||
private static final String HTML_ERROR_MESSAGE = LOG_ERROR_MESSAGE_MISSING_AUTH_CONFIG + "!\nPlease contact your administrator"; | ||
private static final String LOG_ERROR_MESSAGE_NON_EXISTENT_REQUIRED_ACTION = "AuthenticatorConfig references an unknown RequiredAction, please double check if it really exists: 'NON_EXISTENT_REQUIRED_ACTION'"; | ||
|
||
|
||
@Override | ||
public void authenticate(AuthenticationFlowContext context) { | ||
var authenticatorConfig = Optional.ofNullable( | ||
context.getAuthenticatorConfig() | ||
.getConfig() | ||
.get(RequiredActionConstants.CONFIG_REQUIRED_ACTION_KEY)); | ||
if (authenticatorConfig.isEmpty()) { | ||
LOGGER.error(LOG_ERROR_MESSAGE_MISSING_AUTH_CONFIG); | ||
context.getEvent() | ||
.realm(context.getRealm()) | ||
.client(context.getSession().getContext().getClient()) | ||
.user(context.getUser()) | ||
.error(Errors.INVALID_CONFIG); | ||
context.failure(AuthenticationFlowError.INTERNAL_ERROR, | ||
htmlErrorResponse(context, | ||
LOG_ERROR_MESSAGE_MISSING_AUTH_CONFIG + HTML_ERROR_MESSAGE)); | ||
} else if (!doesRequiredActionExists(context, authenticatorConfig.get())) { | ||
LOGGER.error(LOG_ERROR_MESSAGE_NON_EXISTENT_REQUIRED_ACTION); | ||
context.getEvent() | ||
.realm(context.getRealm()) | ||
.client(context.getSession().getContext().getClient()) | ||
.user(context.getUser()) | ||
.error(Errors.INVALID_CONFIG); | ||
context.failure(AuthenticationFlowError.INTERNAL_ERROR, | ||
htmlErrorResponse(context, | ||
LOG_ERROR_MESSAGE_NON_EXISTENT_REQUIRED_ACTION + HTML_ERROR_MESSAGE)); | ||
} else { | ||
context.getUser().addRequiredAction(authenticatorConfig.get()); | ||
context.success(); | ||
} | ||
} | ||
|
||
private boolean doesRequiredActionExists(AuthenticationFlowContext context, String providerId) { | ||
var requiredAction = context.getRealm().getRequiredActionProvidersStream() | ||
.map(RequiredActionProviderModel::getProviderId) | ||
.filter(id -> id.equals(providerId)) | ||
.findFirst(); | ||
return requiredAction.isPresent(); | ||
} | ||
|
||
private Response htmlErrorResponse(AuthenticationFlowContext context, String errorMessage) { | ||
AuthenticationSessionModel authSession = context.getAuthenticationSession(); | ||
return context.form() | ||
.setError(errorMessage, authSession.getAuthenticatedUser().getUsername(), | ||
authSession.getClient().getClientId()) | ||
.createErrorPage(Response.Status.INTERNAL_SERVER_ERROR); | ||
} | ||
|
||
@Override | ||
public void action(AuthenticationFlowContext context) { | ||
} | ||
|
||
@Override | ||
public boolean requiresUser() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean configuredFor(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void setRequiredActions(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) { | ||
//intentionally empty | ||
} | ||
|
||
@Override | ||
public void close() { | ||
|
||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
...e/conciso/keycloak/authentication/required_action/RequiredActionAuthenticatorFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package de.conciso.keycloak.authentication.required_action; | ||
|
||
import org.keycloak.Config; | ||
import org.keycloak.authentication.Authenticator; | ||
import org.keycloak.authentication.AuthenticatorFactory; | ||
import org.keycloak.models.AuthenticationExecutionModel; | ||
import org.keycloak.models.KeycloakSession; | ||
import org.keycloak.models.KeycloakSessionFactory; | ||
import org.keycloak.provider.ProviderConfigProperty; | ||
|
||
import java.util.List; | ||
|
||
public class RequiredActionAuthenticatorFactory implements AuthenticatorFactory { | ||
|
||
public static final String PROVIDER_ID = "required-action-authenticator"; | ||
|
||
@Override | ||
public String getId() { | ||
return PROVIDER_ID; | ||
} | ||
|
||
@Override | ||
public String getDisplayType() { | ||
return "Set Required-Action Authentication"; | ||
} | ||
|
||
@Override | ||
public String getReferenceCategory() { | ||
return "required-action"; | ||
} | ||
|
||
@Override | ||
public boolean isConfigurable() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { | ||
return new AuthenticationExecutionModel.Requirement[]{ | ||
AuthenticationExecutionModel.Requirement.REQUIRED, | ||
AuthenticationExecutionModel.Requirement.DISABLED}; | ||
} | ||
|
||
@Override | ||
public boolean isUserSetupAllowed() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public String getHelpText() { | ||
return "Sets the configured RequiredAction for the authenticating User"; | ||
} | ||
|
||
@Override | ||
public List<ProviderConfigProperty> getConfigProperties() { | ||
//TODO @Robin Maybe multivalued in the future ? | ||
return List.of( | ||
new ProviderConfigProperty( | ||
RequiredActionConstants.CONFIG_REQUIRED_ACTION_KEY, | ||
"Required Action", | ||
"Specifies the Required Action, that will be assigned to the authenticating User", | ||
ProviderConfigProperty.STRING_TYPE, | ||
"", | ||
false, | ||
true) | ||
); | ||
} | ||
|
||
@Override | ||
public Authenticator create(KeycloakSession keycloakSession) { | ||
return new RequiredActionAuthenticator(); | ||
} | ||
|
||
@Override | ||
public void init(Config.Scope scope) { | ||
|
||
} | ||
|
||
@Override | ||
public void postInit(KeycloakSessionFactory keycloakSessionFactory) { | ||
|
||
} | ||
|
||
@Override | ||
public void close() { | ||
|
||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
...main/java/de/conciso/keycloak/authentication/required_action/RequiredActionConstants.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package de.conciso.keycloak.authentication.required_action; | ||
|
||
public final class RequiredActionConstants { | ||
public static final String CONFIG_REQUIRED_ACTION_KEY = "REQUIRED_ACTION"; | ||
} |
1 change: 1 addition & 0 deletions
1
...ion/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
de.conciso.keycloak.authentication.required_action.RequiredActionAuthenticatorFactory |
Oops, something went wrong.