On 8/18/16 5:12 AM, Stian Thorgersen wrote:
On 16 August 2016 at 00:57, Bill Burke <bburke(a)redhat.com
<mailto:bburke@redhat.com>> wrote:
I'm currently working on a new credential SPI that will replace
existing methods on UserProvider and UserModel, as well as
replacing UserCredentialModel, etc. This is a work in progress
where we may see multiple iterations in master. I hope to remain
backward compatible, but can't guarentee I won't break existing
User Federation Providers. Here's an initial writeup to explain
things. Credentials revolve around these 4 events that are
initiated by authentication flows, the admin console, and the
account service.
* Is the user configured for a specific credential type
* Is a credential valid
* What required actions must be taken for an unconfigured
credential type
* update a credential
How each of these events is resolved will depend on the
configuration of the system and these interfaces:
public interface CredentialInput {
String getType();
}
public interface CredentialInputValidator {
boolean supportsCredentialType(String credentialType);
boolean isConfiguredFor(RealmModel realm, UserModel user, String
credentialType);
boolean isValid(RealmModel realm, UserModel user, CredentialInput input);
}
public interface CredentialInputUpdater {
boolean supportsCredentialType(String credentialType);
Set<String> requiredActionsFor(RealmModel realm, UserModel user, String
credentialType);
void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
}
Two different types of components will be able to implement these
interfaces. UserStorageProviders (user federation) and
CredentialProviders. CredentialProviders are components configured
at the realm level. CredentialProviders are responsible for
managing one or more types of credential types and are the bridge
between CredentialInput and where the credential is stored.
UserStorageProvider is always asked first whether it can complete
the requested action, then CredentialProviders are queried in
order of their priority.
Each UserStorageProvider and/or CredentialProvider can implement
the OnUserCache callback interface discussed in my previous custom
caching email. This allows each credential type to decide whether
it will be cached or not along with the user. For example, HOTP
cannot be cached.
So, for example, there will be a KeycloakMobileOTPProvider. This
deals with Google Authenticator and FreeOTP as well as storing
these things within Keycloak storage, it also looks at the OTP
policy of the realm to determine how to update and store the OTP
secret and stuff. There is also a KeycloakPasswordProvider which
hooks into Keycloak storage and the PasswordPolicies set up by the
realm. When a user is cached, the KeycloakPasswordProvider will
add the hashed password to the user cache, the
KeycloakMobileOTPProvider will add the OTP secret to cache if its
not HOTP and needs to maintain a counter.
Let's walk through an authentication flow, specificaly for OTP.
1. Authenticator calls
KeycloakSession.users().isConfiguredFor(realm, user, "OTP"). If
the user was loaded by a UserStorageProvider and that provider
implements the CredentialInputValidator interface,
isConfiguredFor() is called on that. If that returns false, each
CredentialProvider is iterated on to call isConfiguredFor().
2. If OTP is required and not configured for the user, the
Authenticator then calls
KeycloakSession.users().requiredActionsFor(...). Again,
UserStorageProvider is queried first, then the
CredneitalProviders. The first provider that returns a non-empty
set will end the query and the set of required actions will be
returned.
3a. Let's say that in this particular example, the generic OTP
Requried Action screen is invoked. In that case, this required
action provider callsKeycloakSession.users().updateCredential. The
first UserStorageProvider or CredentialProvider that can handle
this credential type will save the credential.
3b. If OTP is configured for user, the OTP is obtained by the
Authenticator and KeycloakSession.users().isValid() method is
called. Again, UserStorageProvider first, then each
CredentialProvider. Each provider is queried until one returns
true or the list is exhausted. FYI, This algorithm allows for
multiple OTP authenticators per user.
** Admin console and Account Service UIs **
Like we do for other components, the UserStorageProvider or
CredentialProvider can optionally provide a list of
ProviderConfigProperties for the admin console and/or account
serviceso that it can create a credential for a specific user.
There will be separate property lists for admin console and
account service. If a specific custom screen is desired, I'm
pretty sure we can just allow the develoepr to plug in their own
$routeProvider for the admin console. We don't have a pluggable
mechanism for the account service yet (or a way to generic render
either). This will need to be developed eventually.
Extending admin console is fine in community, but would be hard to
support.
Generic rendering from a metamodel provided by a REST API is something
we can and should support. It won't be completely pretty, but should
work for most things. If we don't want to support Angular extensions,
then the user can just completely punt and have a completely different
web app to configure a specific component.
Especially as we would eventually have to move to AngularJS 2.0,
which
drastically changes things.
Moving to Angular 2 seems daunting.
https://angular.io/docs/ts/latest/guide/upgrade.html#
I also wonder if we should provide an higher level extension to
register extensions than having to do $routeProviders etc as that
sounds like a fairly low level approach. Do you have any example?
Look at the old ldap provider for an example of overriding a specific
component UI over a fallback generic one. It just has a more specific
route than the generic page.
Account service should be completely scrapped and replaced with
something more modern. The UI needs a complete revamp and it should
also be changed to AngularJS and REST service to make it more
customizable and extensible.
Again, we can focus on generic rendering for Account
Service. Then it
will translate quite easily to whatever we do here.
Bill