Custom Validation SPI

161 views
Skip to first unread message

Thomas Darimont

unread,
Jul 31, 2020, 5:54:30 PM7/31/20
to Keycloak Dev
Hello Keycloak developers,

based on the discussion in https://groups.google.com/forum/#!topic/keycloak-dev/hnGne4qiALw and some requirements from 
for arbitrary custom validations.

A few highlights:
- Introduced "ValidationProvider" SPI that allows providing custom validations rules to Keycloak
- The default "ValidationProvider" implementation "DefaultValidationProvider" ships with a list of built-in validations for 
  user-objects, like username, email, firstname, lastname. Note that you can validate arbitrary objects, not just users.
- The validation rules are expressed as implementations of the functional "Validation" interface. 
- The validation rules can be context-sensitive (enabled / disabled) depending on the contextKeys in the "ValidationContext".
- Validations can be contributed with a custom "ValidationProvider" which registers the validations in a "ValidationRegistry".
- Introduced "ValidatorProvider" SPI that allows providing custom Validation engines to Keycloak
- The default "ValidatorProvider" implementation "DefaultValidatorProvider" uses the Validations form the ValidationProvider 
that are stored in the "ValidationRegistry".
- Users can override internal validations or add additional more specific validations, e.g. to have more strict control about email addresses, etc.

Custom validations can be registered with an identifying key along-side some metadata.

...
    validatorRegistry.register(User.EMAIL, createEmailValidation(), 1100.0,
            ValidationContextKey.PROFILE_UPDATE, ValidationContextKey.REGISTRATION);
...

    protected Validation<String> createEmailValidation() {
        return (key, value, context, problems, session) -> {

            // key -> User.EMAIL
            // value -> the email to validate
            // context -> ValidationContext (current realm, contextKeys, e.g. (profile-update, registration))
            // problems -> List of current ValidationProblem's
            // session -> KeycloakSession to access Keycloak services for validations if necessary

            if (org.keycloak.services.validation.Validation.isBlank(value)) {
                problems.add(ValidationProblem.error(key, Messages.MISSING_EMAIL));
                return false;
            }

            if (!org.keycloak.services.validation.Validation.isEmailValid(value)) {
                problems.add(ValidationProblem.error(key, Messages.INVALID_EMAIL));
                return false;
            }

            return true;
        };
    }

The validations can be easily invoked:

        ValidatorProvider validator = session.getProvider(ValidatorProvider.class);
        ...
        validator.validate(User.EMAIL, formData.getFirst(FIELD_EMAIL), context)
                .onError(res -> res.getErrors().forEach(p -> addError(errors, FIELD_EMAIL, p.getMessage())));

or ...

        validator.validate(User.EMAIL, userModel.getEmail(), context)
                .onError(res -> res.getErrors().forEach(p -> addError(errors, FIELD_EMAIL, p.getMessage())));



Compared to current master:

Do you think this is getting somewhere?

Cheers,
Thomas

Mike M.

unread,
Jul 31, 2020, 7:09:42 PM7/31/20
to Keycloak Dev
Did you check the work being done on user-profile?


This design document contains validation parts too, maybe both these tracks could be combined to obtain the benefits of both?

Thomas Darimont

unread,
Aug 1, 2020, 5:17:55 AM8/1/20
to Keycloak Dev
Thanks for your quick note, yes I read that too as I mentioned in the first paragraph of the previous post ;-).

I think the validation part that's mentioned in milestone m2 of the User-Profile spec can be converted by this and much more. You could also use the validations SPI to validate other objects too, e.g. realm and client settings or even mappers and federation or identity broker settings.

Let's see what comes out of this.

Cheers,
Thomas

Thomas Darimont

unread,
Aug 1, 2020, 6:19:33 AM8/1/20
to Keycloak Dev
I revised the API a little bit:

That's how context specific validations look like:

      ValidatorProvider validator = ...

// At first, we create a "ValidationContext", to describe in which context we want to validate denoted via the "ValidationContextKey". 
// The "ValidationContext" holds a reference to the current realm and might provide additional meta-data to the validation.

        ValidationContext context = new ValidationContext(realm, ValidationContextKey.PROFILE_UPDATE,
                Collections.singletonMap("userNameRequired", userNameRequired));

        List<FormMessage> errors = new ArrayList<>();

// The validator#validate method now accepts a varargs / set of ValidationTargetKeys as a last parameter to denote the validations that should take place, e.g.: User.USERNAME or , User.EMAIL.
// Note that although one can either explicitly request validations, it is also possible to just register multiple validations rules for User.EMAIL validation.

        validator.validate(context, formData.getFirst(FIELD_USERNAME), User.USERNAME)
                .onError(res -> res.getErrors().forEach(p -> addError(errors, FIELD_USERNAME, p.getMessage())));

        validator.validate(context, formData.getFirst(FIELD_FIRST_NAME), User.FIRSTNAME)
                .onError(res -> res.getErrors().forEach(p -> addError(errors, FIELD_FIRST_NAME, p.getMessage())));

        validator.validate(context, formData.getFirst(FIELD_LAST_NAME), User.LASTNAME)
                .onError(res -> res.getErrors().forEach(p -> addError(errors, FIELD_LAST_NAME, p.getMessage())));

        validator.validate(context, formData.getFirst(FIELD_EMAIL), User.EMAIL)
                .onError(res -> res.getErrors().forEach(p -> addError(errors, FIELD_EMAIL, p.getMessage())));

        return errors;

Registration of validators, didn't change

this is from org.keycloak.services.validation.validators.DefaultValidationProvider:

    @Override
    public void register(ValidationRegistry validatorRegistry) {

        // TODO add additional validators

        validatorRegistry.register(User.USERNAME, createUsernameValidation(), 1000.0,
                ValidationContextKey.PROFILE_UPDATE, ValidationContextKey.REGISTRATION);

        validatorRegistry.register(User.EMAIL, createEmailValidation(), 1100.0,
                ValidationContextKey.PROFILE_UPDATE, ValidationContextKey.REGISTRATION);

        validatorRegistry.register(User.FIRSTNAME, createFirstnameValidation(), 1200.0,
                ValidationContextKey.PROFILE_UPDATE);

        validatorRegistry.register(User.LASTNAME, createLastnameValidation(), 1300.0,
                ValidationContextKey.PROFILE_UPDATE);
    }
...


Cheers,
Thomas

Mike M.

unread,
Aug 1, 2020, 7:11:31 AM8/1/20
to Keycloak Dev
Sorry, I missed that link, how embarrassing...

I like the approach. One question that's not immediately obvious to me (sorry if I'm missing it again ;-)): does this allow for validator parameterisation? A lot of validators will have some kind of parameterisation, like e.g. the regex for a regex validator, the maximum length for a max-length validator, the API key for debounce.io's disposable mail check etc.

I think as an end result, it would be great if one could write a somewhat generic validator and have it registered and configured for the realm through the Admin Console. Not having to write a custom validator with hardcoded parameterisation for each Keycloak instance would e.g. allow third party projects to provide Keycloak validators as pluggable SPIs.

Thomas Darimont

unread,
Aug 3, 2020, 4:28:15 PM8/3/20
to Keycloak Dev
No worries,

yes, it is possible to parameterize Validations. For one, you have access to the KeycloakSession to access arbitrary Keycloak services to derive configuration options, 
besides that you have full control over the construction and configuration of your custom validations, so you could access the Config.Scope, ENV Variables or System-Properties.

I'm still collecting the various validations Keycloak uses internally, in order to get a sense for what default validations are needed.
More sophisticated validations like: "Limit the allowed email addresses for a realm" should be done partially based on realm-configuration, as I proposed here: https://github.com/keycloak/keycloak/pull/7306
Another Option would be to allow configuration via Keycloak CLI configuration / System-Property or ENV variable with some realm-name part.

With respect to the GenericValidator idea, I think we're pretty generic already :) A Validation is just a functional interface and with the DelegatingValidation one can even conditionally check whether a validation is required.
Realm level configuration can be accessed via ValdiationContext#getRealm() or ValdiationContext#getRealm().getAttribute(..).

As an example, this is how the "Allowed Email Validation" would look like with the proposed SPI based on a realm attribute:

```
    protected Validation createAllowedEmailAddressValidation() {

        ValidationSupported isAllowedEmailAddressRequired = (key, value, context) ->
                key.equals(User.EMAIL) && value instanceof String && getAllowedEmailAddressesPattern(context) != null;

        Validation isAllowedEmailAddress = (key, value, context, problems, session) -> {
            String input = (String) value;
            if (!input.matches(Objects.requireNonNull(getAllowedEmailAddressesPattern(context)))) {
                problems.add(ValidationProblem.error(key, Messages.INVALID_EMAIL_NOT_ALLOWED));
                return false;
            }
            return true;
        };

        return new DelegatingValidation(isAllowedEmailAddress, isAllowedEmailAddressRequired);
    }

    private String getAllowedEmailAddressesPattern(org.keycloak.validation.ValidationContext context) {

        String allowedEmailAddresses = context.getRealm().getAttribute("allowedEmailAddresses");
        if (allowedEmailAddresses != null && allowedEmailAddresses.trim().length() > 0) {
            return allowedEmailAddresses;
        }
        return null;
    }
```

The (default) validation would then registered after the generic email validation:
```
        validationRegistry.register(User.EMAIL, createAllowedEmailAddressValidation(), VALIDATION_ORDER_USER_EMAIL+100.0,
                ValidationContextKey.PROFILE_UPDATE, ValidationContextKey.REGISTRATION);
```

Cheers,
Thomas

Mike M.

unread,
Aug 3, 2020, 4:51:38 PM8/3/20
to Thomas Darimont, Keycloak Dev
Thanks for the clarification, Thomas! Your approach looks fine to me :-)

--
You received this message because you are subscribed to a topic in the Google Groups "Keycloak Dev" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/keycloak-dev/-XjQu7rn56s/unsubscribe.
To unsubscribe from this group and all its topics, send an email to keycloak-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/keycloak-dev/9207113a-74ac-424e-94b2-16b1922337fcn%40googlegroups.com.

Till Markus (IOC/PDL22)

unread,
Aug 4, 2020, 8:49:04 AM8/4/20
to Thomas Darimont, Keycloak Dev

Hello Thomas,

We would like to team up with you as we currently approaching the very same issues and details in the new user profile.

The good news for you is: You don’t have to look up all the places where validations happen we did already.

 

In the branch you can see all validators which existed in Keycloak https://github.com/keycloak/keycloak/blob/54aeab7c262c361ed1ad6794307f513b1ea0cc8f/services/src/main/java/org/keycloak/userprofile/LegacyUserProfileProvider.java#L70

And the Static validators (which are functions as in your proposal) https://github.com/keycloak/keycloak/blob/54aeab7c262c361ed1ad6794307f513b1ea0cc8f/services/src/main/java/org/keycloak/userprofile/validation/StaticValidators.java

 

According PR https://github.com/keycloak/keycloak/pull/7155/files#diff-8ae01ac5703b69f70d1046eb97178e1cR71

 

As you can see the code has overlaps with your proposal.

As of that I would like to get you in the boat of the new user profile and rebase your efforts against our branch. So we can merge our changes and then yours afterwards.

You will only have to care to embed the validator factories within the profile as there is no context specific code anymore.

 

We also would be open for a short call with you if there is interest on your side and if you feel that’s useful.

 

Mit freundlichen Grüßen / Best regards

Markus Till


Project Delivery Berlin 22 (IOC/PDL22)
Bosch.IO GmbH | Ziegelei 7 | 88090 Immenstaad | GERMANY |
www.bosch.io
Tel. +49 30 726112-354 | Mobil +49 172 57 82 078 | Telefax +49 30 726112-100 |
marku...@bosch-si.com

Sitz: Berlin, Registergericht: Amtsgericht Charlottenburg; HRB 148411 B
Aufsichtsratsvorsitzender: Dr.-Ing. Thorsten Lücke; Geschäftsführung: Dr. Stefan Ferber, Dr. Aleksandar Mitrovic, Yvonne Reckling

--
You received this message because you are subscribed to the Google Groups "Keycloak Dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to keycloak-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/keycloak-dev/CAF1uOF28qhffhjHC1254196JJ2jRAUywDMyUSGHeTLiZJF%3Ds0g%40mail.gmail.com.

Thomas Darimont

unread,
Aug 4, 2020, 12:36:19 PM8/4/20
to Till Markus (IOC/PDL22), Keycloak Dev
Hi Markus,

Honestly, I wasn't aware of this PR... I did read the user-profile spec a few times, but I didn't check if there is already code for this.
So after a quick look, it seems that the validation machinery in your PR is quite coupled to the profile structures introduced by the PR. 
The proposed Validation SPI seems to be much more generic and could be a good foundation for the user-profile SPI :) 

So yes, sure, let's join forces!

I'm fine with a call to sort-out the details, but I'd prefer a video meeting. I have time tomorrow afternoon (4pm-5pm+)  and could set up a zoom call if you like. Would that work for you guys?

Cheers,
Thomas

Thomas Darimont

unread,
Aug 7, 2020, 7:36:06 PM8/7/20
to Keycloak Dev

Hello Keycloak Developers,


I made some progress with the Validation SPI and I just wanted to give you a quick update on the current state of the API.


If you have questions about the validation SPI or some additional ideas, I'm looking forward to hearing from you.


# Validation SPI 


## Rationale

Currently, validation logic is scattered and duplicated all around the Keycloak codebase with partially significant differences concerning the implementation although the validation

logic is the same. Also reusing built-in validation logic for custom extensions usually means a lot of duplicated code, which needs to be maintained for Keycloak upgrades.


This proposal introduces a new extensible validation SPI with a uniform facility to register validation rules and to perform validations of arbitrary values while being able to

select which validations should be executed in which context. This eases to reuse of existing validation logic and allows the augmentation of the built-in validations with custom validation logic.


This is, of course, no replacement for small static utility functions, e.g. to check whether a value is not null or empty e.g. but rather to describe more complex validation rules that

need to be augmented for some use-cases.


## Why not just using bean-validation

Well I thought about this for some time but after a quick prototype, I concluded that it would make more sense to go with a custom implementation that leverages the existing Keycloak infrastructure for providing custom extensions and better integration with the existing Keycloak structures. Note that it is still possible to provide a validation/validator implementation that leverages bean validation. Also, bean-validation still works quite well for declarative validation rules in JAX-RS endpoints.


# Some features of the Validation SPI

- Support for Validation of arbitrary Keycloak components on object and property level / single value level

- Functional interface based Validation function definitions

- Built-in validations for common values like usernames, email addresses, etc.

- Support for user-defined custom validations via ValidationProvider SPI

- Validation Problems can be reported as Errors or Warnings

- Validations can be performed in bulk mode (all problems are collected for a value) or in short-circuit mode (validation exits on the first problem).

- Support for nested validations, custom Validations can use other Validations within a validation function

- Support for validation ordering: validations can be added before or after existing Validations to augment validations, e.g. validate an email for correct format and then check if the email domain is allowed.

- Support for replacing built-in validations to completely replace a validation if necessary.



## Core Abstractions (in server-spi Module)


- Validation (Functional Interface, describes a Validation)

- ValidatorProvider (Interface, SPI, denotes a Validator that can validate a given value according to some Validations governed by the given ValidationKey in a ValidationContext)

- ValidationKey (references a set of validations, like User.USERNAME, User.EMAIL, User.MOBILE_PHONE, etc.)

- ValidationProvider (Interface, SPI, provides built-in a custom Validations to the validation infrastructure)

- ValidationContext (A context a validation is performed in, provides access to the current realm, client, session and validation attributes, etc.)

- ValidationResult (Contains the validation state (valid or not) and access to problems found during the validation run)

- ValidationProblem (Denotes a concrete problem found during validation (Errors, Warnings) for a ValidationKey an i18n errorMessage reference)

- ValidationContextKey (Refers to a particular validation context like USER_PROFILE_UPDATE or USER_REGISTRATION, this allows different validations for a validation key in different contexts)

- ValidationRegistry (Allows to register new Validations for a ValidationKey which can be bound to one or more ValidationContextKeys)


## Default implementations (in keycloak-services Module)

- DefaultValidatorProvider (can discover provided Validations and use them to validate values in a validation context)

- DefaultValidationRegistry (Allows to register new Validations

- DefaultValidationProvider (Registers a set of default validation primitives, like validations for email, username, firstname, lastname etc.)


# API examples


## Registering a new Validation via SPI


public class DefaultValidationProvider implements ValidationProvider {


  @Override

  public void register(MutableValidationRegistry registry) {

...

    registry.register(createEmailValidation(), ValidationKey.User.EMAIL,

        ValidationContextKey.User.PROFILE_UPDATE, ValidationContextKey.User.REGISTRATION);

...

  }


...


  protected Validation createEmailValidation() {

    return (key, value, context) -> {


      String input = value instanceof String ? (String) value : null;


      if (org.keycloak.services.validation.Validation.isBlank(input)) {

        context.addError(key, Messages.MISSING_EMAIL);

        return false;

      }


      if (!org.keycloak.services.validation.Validation.isEmailValid(input)) {

        context.addError(key, Messages.INVALID_EMAIL);

        return false;

      }


      return true;

    };

  }

}


## Using the Validator to Validate a value

ValidatorProvider validator = context.getSession().getProvider(ValidatorProvider.class);


ValidationContext context = new ValidationContext(realm, ValidationContextKey.User.PROFILE_UPDATE,

     Collections.singletonMap("userNameRequired", userNameRequired));


List<FormMessage> errors = new ArrayList<>();

validator.validate(context, formData.getFirst(FIELD_EMAIL), ValidationKey.User.EMAIL)

       // this can be stream-lined in line with the upcoming user-profile changes

       .accept(res -> res.getErrors().forEach(p -> addError(errors, FIELD_EMAIL, p.getMessage())));

...



PR for current Validation SPI proposal: https://github.com/keycloak/keycloak/pull/7324


## Additional Validator examples


// validation with KeycloakSession access

  protected Validation uniqueEmailValidation() {

    return (key, value, context) -> {


      if (context.getRealm().isDuplicateEmailsAllowed()) {

        return true;

      }


      String input = value instanceof String ? (String) value : null;

      if (input == null) {

        context.addError(key, Messages.MISSING_EMAIL);

      }


      UserModel userByEmail = context.getSession().users().getUserByEmail(input, context.getRealm());

      return userByEmail == null;

    };

  }


// validation with nested validation of a complex object (UserModel), e.g. using the built-in email validation


  protected Validation createProfileValidation() {

    return (key, value, context) -> {


      UserModel input = value instanceof UserModel ? (UserModel) value : null;


      if (input == null) {

        context.addError(key, Messages.INVALID_USER);

        return false;

      }


      if (!"content".equals(input.getFirstAttribute("FAKE_FIELD"))) {

        context.addError(key, "FAKE_FIELD_ERRORKEY");

        return false;

      }


      boolean emailValid = context.validateNested(ValidationKey.User.EMAIL, input.getEmail());

      if (!emailValid) {

        context.addError(key, Messages.INVALID_EMAIL);

      }


      return emailValid;

    };

  }


Some more API examples can be found in the DefaultValidatorProviderTest: https://github.com/keycloak/keycloak/blob/7c9ca79ef52072ea12602705d53b346addd3535d/services/src/test/java/org/keycloak/services/validation/DefaultValidatorProviderTest.java


Additionally, some integration examples can be seen here:


# User Profile Proposal integration

Markus Till, Martin Idel, and me had a zoom call this week where we discussed the proposed API and how it could serve as a foundation for the user-profile SPI proposal.

We concluded that a great portion of the current implementation of the user-profile proposal can be implemented on top of the generic validation spi.


We're currently working on putting the two worlds together. 


User-Profile SPI PR: https://github.com/keycloak/keycloak/pull/7155

User-Profile SPI Proposal: https://github.com/keycloak/keycloak-community/blob/master/design/user-profile.md


Cheers,

Thomas

Mike M.

unread,
Aug 8, 2020, 7:07:40 AM8/8/20
to Keycloak Dev
Hi guys,

That looks very promising! Thanks for the work you're doing, I'm already looking forward to seeing all of this integrated in some upcoming KC release ;-)

There's just one thing I noticed while reading through your description, Thomas:

    registry.register(createEmailValidation(), ValidationKey.User.EMAIL,
        ValidationContextKey.User.PROFILE_UPDATE, ValidationContextKey.User.REGISTRATION);

If I understand it right, we'd have to enumerate all ValidationContextKeys the validator should be applied in.

It certainly makes sense to allow for a validator to react differently, or even not be triggered at all depending on the context the validation runs in. However, I guess a very common case for validation on a domain object (like e.g. a user) would be to have it enforced everywhere throughout the system. If, for example, I want usernames to never exceed 40 characters, I want to be sure that there is no way for a user to break that assumption. To my understanding, ValidationContextKeys are somewhat dynamic: A custom SPI or a new feature in a new KC version might define their own ValidationContextKeys. What would that mean for all of the existing validation registrations? Sure, if I want a validation to only run for a subset of ValidationContextKeys, I'd have to update that subset. But if the "please have this validation run everywhere"-case really is a common one, I think there might be a need for registering validations unconditionally of ValidationContextKeys. What do you think?

Best regards,
Mike

To unsubscribe from this group and all its topics, send an email to keycl...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Keycloak Dev" group.

To unsubscribe from this group and stop receiving emails from it, send an email to keyclo...@googlegroups.com.

Thomas Darimont

unread,
Aug 10, 2020, 3:23:43 AM8/10/20
to Mike M., Keycloak Dev
Hello Mike,

I thought about that use-case too for a while. To be able to define validations which should run in multiple or all contexts I introduced a hierarchical ValidationContextKey. 

public interface ValidationContextKey {
...
    ValidationContextKey DEFAULT_CONTEXT_KEY = new BuiltInValidationContextKey("", null);
    ValidationContextKey CLIENT_DEFAULT_CONTEXT_KEY = new BuiltInValidationContextKey("client", ValidationContextKey.DEFAULT_CONTEXT_KEY);
 ...
    ValidationContextKey USER_DEFAULT_CONTEXT_KEY = new BuiltInValidationContextKey("user", ValidationContextKey.DEFAULT_CONTEXT_KEY);
    ValidationContextKey USER_RESOURCE_UPDATE_CONTEXT_KEY = new BuiltInValidationContextKey("user.resource_update", USER_DEFAULT_CONTEXT_KEY);
...

With this in place, one can define Validations that should run every time (ValidationContextKey.DEFAULT_CONTEXT_KEY) or just in specific contexts, e.g. whenever a user is validated (ValidationContextKey.USER_DEFAULT_CONTEXT_KEY).

Cheers,
Thomas

To unsubscribe from this group and stop receiving emails from it, send an email to keycloak-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/keycloak-dev/b72870e4-591f-439a-8569-d59882433c21o%40googlegroups.com.

Mike Meessen

unread,
Aug 10, 2020, 6:34:20 AM8/10/20
to Keycloak Dev
Yes, that's even better. Looks great! 👍

Pedro Igor Craveiro e Silva

unread,
Aug 10, 2020, 8:03:07 AM8/10/20
to Mike Meessen, Keycloak Dev
Yeah, it does .. Nice work Thomas.

I did a few minor comments but in general, I liked it very much. Let's se what others think about it.

To unsubscribe from this group and stop receiving emails from it, send an email to keycloak-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/keycloak-dev/8e60ccf0-87d0-4203-8063-6720059af8f3o%40googlegroups.com.

Thomas Darimont

unread,
Aug 10, 2020, 8:30:46 AM8/10/20
to Keycloak Dev
Hi folks, 

Thanks for the feedback, I'll look into the requested changes this evening.

I just wanted to clarify some use cases for the validations

1) Simple value validation

Denotes a validation of a single value like a username, an user email address, a client redirect URI, or an arbitrary component property against a set of validation rules referenced by a validation key in a certain ValidationContext.

The idea is to provide a way to have explicit validations on the level of a single value.

e.g.:
```
ValidationContext context = new ValidationContext(USER_REGISTRATION_CONTEXT_KEY);
ValidationResult result = validator.validate(context, "test@localhost", ValidationKey.USER_EMAIL);
// inspect problems from result
...
```

2) Complex value validation

Denotes a validation of a compound object like a UserModel, RealmModel, or ClientModel, etc.

The idea is to provide support for explicit validations on the level of a complex object like a UserModel, ClientModel, etc.,
while adhering to a set of validation rules that are built-in to Keycloak which still can be extended by users.
Validations for compound objects can of course reuse existing simple or complex value in nested validations.

This would allow us to run validations before saving / updating complex objects with a single call to: `validator.validate(...)
e.g:
```
ValidationContext context = new ValidationContext(USER_REGISTRATION_CONTEXT_KEY);
ValidationResult result = validator.validate(context, user, ValidationKey.USER);
// inspect problems from result
...
```
Note that for well-known component types, like UserModel, RealmModel or ClientModel we could even shorten the validation a bit to:
```
ValidationResult result = validator.validate(context, user);
```
if we define default ValidationKey mappings for certain types like UserModel -> ValidationKey.USER.

Cheers,
Thomas

Pedro Igor Craveiro e Silva

unread,
Aug 10, 2020, 8:42:09 AM8/10/20
to Thomas Darimont, Keycloak Dev
Yeah, that is what I meant by implementation details in my PR. I did not want to nitpick your initial changeset but we could also infer the context key from the object itself, passed to the validate method. So if you pass `UserModel` we infer that the context key is any context related to the user, potentially making the call to validate more simple: `validator.validate(user)`.

Another way of thinking about it is leveraging/integrating somehow validation with the event provider so that we could also infer the context from events when they are configured prior to processing some action. But like I said, what you did is good enough IMO and any improvement we can do later. We just need to make sure the API is good enough to accommodate any possible and future improvements. 
 

Pedro Igor Craveiro e Silva

unread,
Aug 10, 2020, 8:42:27 AM8/10/20
to Thomas Darimont, Keycloak Dev
Sorry, "in your PR" :)

Stian Thorgersen

unread,
Aug 19, 2020, 3:30:53 AM8/19/20
to Pedro Igor Craveiro e Silva, Thomas Darimont, Keycloak Dev
Haven't had time to look at this lengthy thread in details, nor the code, but at a quick glance it does look promising. 

A couple points here that should be considered:

* User Profile - it is paramount that this work is aligned with the user profile work [1] 
* Client Policies - another thing that should be aligned with this work [2]
* Custom validation - it should be both possible to add custom validation through code, but also through config. A good example here is user profile where someone should be able to use/configure built-in validators when adding custom attributes
* How to utilise this to improve on out of the box validation for realm (including things like IdPs, user fed, authentication flows, clients, etc.). Ideally, we would like one validation framework that can easily be applied everywhere

It may be worth doing a write-up of this proposal as a design document in https://github.com/keycloak/keycloak-community/tree/master/design as this thread is becoming rather lengthy and hard to follow.


Reply all
Reply to author
Forward
0 new messages