Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/documentation/server_development/topics.adoc
Original file line number Diff line number Diff line change
@@ -1,30 +1,61 @@
include::topics/preface.adoc[]

include::topics/admin-rest-api.adoc[]

include::topics/identity-brokering.adoc[]

include::topics/identity-brokering/tokens.adoc[]

include::topics/identity-brokering/account-linking.adoc[]

include::topics/providers.adoc[]

ifeval::[{project_community}==true]

include::topics/extensions.adoc[]

include::topics/auth-spi.adoc[]

include::topics/password-policy-spi.adoc[]

include::topics/action-token-handler-spi.adoc[]

include::topics/events.adoc[]

include::topics/saml-role-mappings-spi.adoc[]

endif::[]

include::topics/user-storage.adoc[]

include::topics/user-storage/entity-manager.adoc[]

include::topics/user-storage/provider-interfaces.adoc[]

include::topics/user-storage/provider-capability-interfaces.adoc[]

include::topics/user-storage/model-interfaces.adoc[]

include::topics/user-storage/packaging.adoc[]

include::topics/user-storage/simple-example.adoc[]

include::topics/user-storage/configuration.adoc[]

include::topics/user-storage/registration-query.adoc[]

include::topics/user-storage/augmenting.adoc[]

include::topics/user-storage/import.adoc[]

include::topics/user-storage/cache.adoc[]

include::topics/user-storage/javaee.adoc[]

include::topics/user-storage/rest.adoc[]

include::topics/user-storage/migration.adoc[]

include::topics/user-storage/stream-interfaces.adoc[]

include::topics/vault.adoc[]
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[[_action_token_handler_spi]]
== Action Token Handler SPI

An action token is a special instance of Json Web Token (JWT) that permits its bearer to perform some actions, e.g. to
include::partial-private-spi.adoc[]

An action token is a special instance of JSON Web Token (JWT) that permits its bearer to perform some actions, e.g. to
reset a password or validate e-mail address. They are usually sent to users in form of a link that points to an endpoint
processing action tokens for a particular realm.

Expand Down Expand Up @@ -136,7 +138,7 @@ public class DemoActionToken extends DefaultActionToken {
To plug your own action token and its handler, you need to implement few interfaces on server side:

* `org.keycloak.authentication.actiontoken.ActionTokenHandler` - actual handler of action token for a particular
action (i.e. for a given value of `typ` token field).
action (i.e. for a given value of `typ` token field).
+
The central method in that interface is `handleToken(token, context)` which defines actual operation executed upon
receiving the action token. Usually it is some alteration of authentication session notes but generally it can be
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[[_auth_spi]]
== Authentication SPI

include::partial-private-spi.adoc[]

{project_name} includes a range of different authentication mechanisms: kerberos, password, otp and others.
These mechanisms may not meet all of your requirements and you may want to plug in your own custom ones.
Expand Down
2 changes: 2 additions & 0 deletions docs/documentation/server_development/topics/events.adoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[[_events]]
== Event Listener SPI

include::partial-private-spi.adoc[]

Writing an Event Listener Provider starts by implementing the `EventListenerProvider` and `EventListenerProviderFactory` interfaces. Please see the Javadoc
and examples for complete details on how to do this.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[[_extensions]]

== Extending the server

The {project_name} SPI framework offers the possibility to implement or override particular built-in providers. However {project_name}
Expand Down Expand Up @@ -34,7 +33,7 @@ For details on how to package and deploy a custom provider, refer to the <<_prov
NOTE: While it is possible to install other JAX-RS components via the providers extension mechanism, such as filters and interceptors, these are not officially supported.

NOTE: To align with Keycloak's built-in transaction per request lifecycle, you should use only blocking, synchronous endpoints. Examples of asynchronous endpoints include
the usage of `AsyncResponse`, or returning `CompletableFuture` or `CompletionStage`.
the usage of `AsyncResponse`, or returning `CompletableFuture` or `CompletionStage`.

[[_extensions_spi]]
=== Add your own custom SPI
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NOTE: This is an internal SPI. While it was stable in the previous releases, any new release of {project_name} can change it without prior notice.
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
[[_password_policy_spi]]
== Password Policy SPI

include::partial-private-spi.adoc[]

{project_name} ships with a set of built-in password policies, such as minimum length or required special characters, that administrators combine per realm. The link:{adminguide_link}#_password-policies[{adminguide_name}] describes how to configure the built-in policies. When the built-in policies do not cover a requirement, you can implement your own policy with the Password Policy SPI.

A password policy provider validates a candidate password when a user updates credentials, when an administrator sets a password through the Admin Console or the Admin REST API, and when a user registers. A realm stores its password policy as one string of tokens, for example `length(12) and specialChars(2)`. Each token holds the configuration value for one policy, and your provider receives that value as a single string.

=== Implementing a password policy provider

You need to implement the `PasswordPolicyProvider` and `PasswordPolicyProviderFactory` interfaces from the `org.keycloak.policy` package.

The provider performs the validation. Returning `null` accepts the password, while returning a `PolicyError` rejects it:

[source,java]
----
package org.acme.policy;

import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.policy.PasswordPolicyConfigException;
import org.keycloak.policy.PasswordPolicyProvider;
import org.keycloak.policy.PolicyError;

public class MaxAcmePasswordPolicyProvider implements PasswordPolicyProvider {

private final KeycloakContext context;

public MaxAcmePasswordPolicyProvider(KeycloakContext context) {
this.context = context;
}

@Override
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}

@Override
public PolicyError validate(String user, String password) {
int maxOccurrences = context.getRealm().getPasswordPolicy()
.getPolicyConfig(MaxAcmePasswordPolicyProviderFactory.ID);
// return null when the password is acceptable, or a PolicyError with a message key
if (countOccurrences(password, "acme") > maxOccurrences) {
return new PolicyError("invalidPasswordMaxAcmeMessage", maxOccurrences);
}
return null;
}

private int countOccurrences(String password, String search) {
return (password.length() - password.replace(search, "").length()) / search.length();
}

@Override
public Object parseConfig(String value) {
// convert the raw configuration string, and reject invalid values
Integer maxOccurrences = parseInteger(value, 1);
if (maxOccurrences < 0) {
throw new PasswordPolicyConfigException("Number must be zero or greater");
}
return maxOccurrences;
}

@Override
public void close() {
}
}
----

The value a provider returns from `parseConfig` is cached in the parsed realm password policy and is available through `PasswordPolicy#getPolicyConfig(String providerId)`. Throw a `PasswordPolicyConfigException` from `parseConfig` to reject an invalid configuration value; the Admin Console shows the error to the administrator when the policy is saved.

The factory describes the policy to the server and to the Admin Console:

[source,java]
----
package org.acme.policy;

import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.policy.PasswordPolicyProvider;
import org.keycloak.policy.PasswordPolicyProviderFactory;

public class MaxAcmePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {

public static final String ID = "maxAcme";

@Override
public String getId() {
return ID;
}

@Override
public PasswordPolicyProvider create(KeycloakSession session) {
return new MaxAcmePasswordPolicyProvider(session.getContext());
}

@Override
public String getDisplayName() {
return "Maximum Acme Occurrences";
}

@Override
public String getConfigType() {
return PasswordPolicyProvider.INT_CONFIG_TYPE;
}

@Override
public String getDefaultConfigValue() {
return "1";
}

@Override
public boolean isMultiplSupported() {
return false;
}

@Override
public void init(Config.Scope config) {
}

@Override
public void postInit(KeycloakSessionFactory factory) {
}

@Override
public void close() {
}
}
----

The factory methods control how the policy appears in the *Authentication* -> *Policies* tab of the Admin Console:

* `getDisplayName()` returns the label shown in the *Add policy* drop-down.
* `getConfigType()` declares how the configuration value is edited. The SPI defines two configuration types: `PasswordPolicyProvider.INT_CONFIG_TYPE`, rendered as a numeric input, and `PasswordPolicyProvider.STRING_CONFIG_TYPE`, rendered as a text input. Return `null` when the policy has no editable configuration value; the Admin Console then shows a disabled on/off indicator instead of an input field.
* `getDefaultConfigValue()` returns the value that pre-fills the input when an administrator adds the policy.
* `isMultiplSupported()` reports whether the policy supports multiple instances. The Admin Console currently offers each policy at most once and the realm stores one configuration value per policy, so return `false`.

=== Policies with multiple configuration parameters

The SPI does not define structured configuration: the realm password policy holds one configuration string per policy. A policy that needs several parameters therefore has to define its own format inside that single value. One approach is a comma separated list, which the Admin Console accepts inside the parentheses of a policy token: a policy configured as `myPolicy(3,true)` receives the string `3,true` in `parseConfig`. Parse and validate the individual parameters there, apply defaults for the optional trailing ones, and return an object that holds the parsed values.

=== Returning localized error messages

The message key passed to `PolicyError` is resolved against the message bundles of the theme that reports the rejection. Login flows, such as registration and required password updates, use the login theme, while the Admin Console and the Admin REST API resolve password errors from the admin theme. Bundle the messages with the provider itself by placing a `theme-resources/messages/messages_en.properties` file in the provider JAR. Files under `theme-resources/messages` are merged into the message bundles of every theme, so one file covers all of these flows:

[source]
----
invalidPasswordMaxAcmeMessage=Invalid password: must not contain more than {0} occurrences of "acme".
----

See https://www.keycloak.org/ui-customization/themes[Working with themes: Theme resource provider] for more details on theme resources. Add a `messages_<locale>.properties` file per supported locale.

=== Providing help text for the Admin Console

The Admin Console shows a tooltip for each policy row, resolved from the `admin-messages` bundle using the key `passwordPoliciesHelp.<policy id>`. Ship the key from the provider JAR in a `theme-resources/messages/admin-messages_en.properties` file:

[source]
----
passwordPoliciesHelp.maxAcme=Maximum number of times the string "acme" may occur in the password.
----

Without this key the Admin Console shows the raw message key as the tooltip. Add an `admin-messages_<locale>.properties` file per supported locale.

=== Registering the provider

Register the factory with a `META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory` file in the provider JAR that lists the fully qualified name of the factory class:

[source]
----
org.acme.policy.MaxAcmePasswordPolicyProviderFactory
----

Deploy the JAR like any other provider, as described in <<_providers,Service Provider Interfaces (SPI)>>. After the server restarts, the policy appears in the *Add policy* drop-down of the *Authentication* -> *Policies* tab.
Loading