I am looking for any way to get the firstname or the lastname of a new self-registered user on keyclock using a User Storage Provider ?
In my case, i'm working with Keycloak 12 and an old legacy MySQL database where I have to store all my users.
Implementing into my SPI the UserRegistrationProvider.addUser(RealmModel realm, String username) and the CredentialInputUpdater.updateCredential(RealmModel realm, UserModel user, CredentialInput input) allows me to catch the username and the password ... but I can't get any other basic information such as firstname/lastname ?
Here are my two implementation of CredentialInputUpdater and UserRegistrationProvider :
#Override
public UserModel addUser(final RealmModel realmModel, final String username) {
Integer id = userRepository.createUser(username, "password");
User user = null;
if (id != null) {
user = new User(id.toString(), username, "firstName", "userName", "password");
}
return user != null ? new UserAdapter(session, realmModel, model, user) : null;
}
#Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) {
return false;
}
UserCredentialModel cred = (UserCredentialModel) input;
return userRepository.updateCredentials(user.getUsername(), cred.getChallengeResponse());
}
Any idea on how to get these two data ?
A bit late for your question, but I ran into a similar problem. My use case was the following : I had to call an external API from the addUser method while providing both username and password.
After debugging Keycloak I found out you can retrieve every parameters filled in the form inside the Keycloak session (or the context).
Here is an example for the password :
String password = this.session.getContext()
.getContextObject(HttpRequest.class)
.getDecodedFormParameters()
.getFirst("password");
Hopefully, this will help other people.
After some days of research, I finally found the solution.
I'm not sure if it works on keycloak 12, but it does on keycloak 18.
This comment give me the point.
So in addUser you don't have to persit the new user, you only have to create your UserAdapter and just before return UserModel I added:
javax.enterprise.inject.spi.CDI.current().getBeanManager().fireEvent(userAdapter);
and in the same class I added the event listener:
public void observeUserAddedEvent(#Observes(during = TransactionPhase.BEFORE_COMPLETION) UserModel addedUser) {
// persist the "post populated" user
}
Related
I have implemented a custom user storage provider for federating users from our database.
I want to manage OTP for those users via keycloak, when I set the OTP to required in the flow and Configure OTP as required action the otp form is shown after federated user login, but when I try to setup the OTP I receive the error user is read only for this update.
How can I allow read only federated users to allow OTP configuration via keycloak?
2022-01-31 17:00:12,704 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-669) Uncaught server error: org.keycloak.storage.ReadOnlyException: user is read only for this update
at org.keycloak.keycloak-server-spi#15.1.1//org.keycloak.storage.adapter.AbstractUserAdapter.removeRequiredAction(AbstractUserAdapter.java:77)
at org.keycloak.keycloak-services#15.1.1//org.keycloak.services.resources.LoginActionsService.processRequireAction(LoginActionsService.java:1044)
at org.keycloak.keycloak-services#15.1.1//org.keycloak.services.resources.LoginActionsService.requiredActionPOST(LoginActionsService.java:967)
the user adapter is
public class UserAdminAdapter extends AbstractUserAdapter {
private final CustomUser user;
public UserAdminAdapter(
KeycloakSession session,
RealmModel realm,
ComponentModel storageProviderModel,
CustomUser user) {
super(session, realm, storageProviderModel);
this.user = user;
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public Stream<String> getAttributeStream(String name) {
Map<String, List<String>> attributes = getAttributes();
return (attributes.containsKey(name)) ? attributes.get(name).stream() : Stream.empty();
}
#Override
protected Set<GroupModel> getGroupsInternal() {
if (user.getGroups() != null) {
return user.getGroups().stream().map(UserGroupModel::new).collect(Collectors.toSet());
}
return new HashSet<>();
}
#Override
protected Set<RoleModel> getRoleMappingsInternal() {
if (user.getRoles() != null) {
return user.getRoles().stream().map(roleName -> new UserRoleModel(roleName, realm)).collect(Collectors.toSet());
}
return new HashSet<>();
}
#Override
public boolean isEnabled() {
return user.isEnabled();
}
#Override
public String getId() {
return StorageId.keycloakId(storageProviderModel, user.getUserId() + "");
}
#Override
public String getFirstAttribute(String name) {
List<String> list = getAttributes().getOrDefault(name, Collections.emptyList());
return list.isEmpty() ? null : list.get(0);
}
#Override
public Map<String, List<String>> getAttributes() {
MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
attributes.add(UserModel.USERNAME, getUsername());
attributes.add(UserModel.EMAIL, getEmail());
attributes.add(UserModel.FIRST_NAME, getFirstName());
attributes.add(UserModel.LAST_NAME, getLastName());
attributes.addAll(user.getAttributes());
return attributes;
}
#Override
public String getFirstName() {
return user.getFirstName();
}
#Override
public String getLastName() {
return user.getLastName();
}
#Override
public String getEmail() {
return user.getEmail();
}
}
The reason is that in your UserAdminAdapter class, you have not implemented the removeRequiredAction and addRequiredAction methods. The message you're receiving is from the default implementation provided by the base class. You should either implement these methods yourself and store the required actions in your underlying storage, OR consider extending your class from AbstractUserAdapterFederatedStorage instead which delegates all such functionalities to the internal Keycloak implementation.
FULL OTP support in my external DB
Well, finally after more than a week I got this working with Keycloak 18.0. What do you need to do?, simply, you have to implement each and every step in the authentication workflow:
Create your user storage SPI
Implement Credential Update SPI
Implement a custom Credential Provider SPI
Implement a custom Required Action SPI
Implement your authenticator SPI
Implement your forms (I kinda used the internal OTP forms in KC)
Enable your Required action
Create a copy of the browser workflow and plaster there your authenticator
And what do we get with this?
We get a fully customizable OTP authenticator (realm's policy pending...)
You can use that code for verification in your app (it's in your db)
You can setup users for OTP authentication in your app (no KC admin page involved, so, you can leave the admin page outside the firewall)
In my opinion, this is kinda annoying, since there are a lot of loops we have to make to be able to store our data locally and how to deal with the integrated OTP forms (for a "natural look"), but it gives me full control over my OTP integration, also, I can backup my database and their OTP authentication is still there, so, if I have a failure in a KC upgrade or it gets corrupted, I still have all that data.
Lastly, heres what it should look like when your manager has the custom OTP authentication
In Keycloak I implemented a custom UserLookupProvider and a CredentialInputValidator. The legacy system which I want to get connected to keycloak is only able to check if a user exist if I supply the system with the user ID and the password.
Unfortunately I dont have access to the password in the getUserByUsername function. Therefore I cant check if the user exists in the legacy system.
I found this similar question without an answer: https://lists.jboss.org/pipermail/keycloak-user/2019-May/018271.html
I found an answer:
I've found the idea here How to implement Recaptcha on keycloak login page (https://github.com/raptor-group/keycloak-login-recaptcha/blob/master/src/main/java/org/keycloak/marjaa/providers/login/recaptcha/authenticator/RecaptchaUsernamePasswordForm.java)
I created a UserNamePasswordForm
public class RecaptchaUsernamePasswordForm extends UsernamePasswordForm implements Authenticator{
#Override
protected Response createLoginForm( LoginFormsProvider form ) {
return super.createLoginForm( form );
}
#Override
public void authenticate(AuthenticationFlowContext context) {
super.authenticate(context);
}
#Override
public void action(AuthenticationFlowContext context) {
context.getSession().setAttribute("password", context.getHttpRequest().getDecodedFormParameters().get("password").get(0));
super.action(context);
}
}
I can access the password now everywhere:
#Override
public UserModel getUserByUsername(String username, RealmModel realm) {
session.getAttribute("password")
if (isAuthenticatedInUls(username,session.getAttribute("password"))) {
return migrate(username, username, realm);
//
}
return null;
}
I want to keep some information in Keycloak as custom user attributes.
Some of them should be managed by the user itself. Other attributes should be managed only by a Keycloak administrator. Attributes managed by the administrator should be read-only visible in the "Edit account" web page for the user.
I went through the guide to add custom user attributes in this page and customized the "Edit account" web page.
My question is:
Is it ensured that the user cannot change the attribute that is meant as read-only for the user? E.g. by submitting a form where he/she sends correct data that will be automatically mapped on the server side to the user attribute.
For what you've said, it seems that you have three choices.
One would be to keep the keycloak "Edit Account" page and use an update profile listener to check what attributes are stored or which ones are updated by who, something like this:
public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
#Override
public InitiatedActionSupport initiatedActionSupport() {
return InitiatedActionSupport.SUPPORTED;
}
#Override
public void evaluateTriggers(RequiredActionContext context) {
}
#Override
public void requiredActionChallenge(RequiredActionContext context) {
Response challenge = context.form()
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
}
// Check the custom attribute 1 not being modified by the user
#Override
public void processAction(RequiredActionContext context) {
EventBuilder event = context.getEvent();
event.event(EventType.UPDATE_PROFILE);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModel user = context.getUser();
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1")
if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
Response challenge = context.form()
.setError("User cannot change the attribute")
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
context.success();
}
#Override
public void close() {
}
#Override
public RequiredActionProvider create(KeycloakSession session) {
return this;
}
#Override
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
if (displayType == null) return this;
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
return ConsoleUpdateProfile.SINGLETON;
}
#Override
public void init(Config.Scope config) {
}
#Override
public void postInit(KeycloakSessionFactory factory) {
}
#Override
public String getDisplayText() {
return "Update Profile";
}
#Override
public String getId() {
return UserModel.RequiredAction.UPDATE_PROFILE.name();
}
}
What I don't know is if this listener will be called when you update the profile from your client application too. If it gets called, you'll need to check which is the logged in client, if it's the public client do not let update the attributes, if it's your service client, let it.
The second one would be to only let your service client update the user profiles and make a custom view in your application which sends a form POST to your client, instead of to keycloak directly. This way you can validate it in the service before sending it to keycloak.
The third one is to implement a FormAction interface, which would allow you to validate the incoming form at server side:
The core interface you have to implement is the FormAction interface. A FormAction is responsible for rendering and processing a portion of the page. Rendering is done in the buildPage() method, validation is done in the validate() method, post validation operations are done in success().
#Override
public void validate(ValidationContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModel user = context.getUser();
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1")
if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
Response challenge = context.form()
.setError("User cannot change the attribute")
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
context.success();
}
perform an update to version 12.0.4.
There were some issues < 12.0.4 with dropping all attributes if user updates his profile.
Additionally with 12.0.4 you can create user- and admin-read only attributes.
Check documentation: https://www.keycloak.org/docs/latest/server_admin/#_read_only_user_attributes
Cheers
I am using EF 6 Code First and creating a User and A Role and associating the user to the Role in Seed Method.
AppUserManager userManager = new AppUserManager(new UserStore<AppUser>(context));
AppRoleManager roleManager = new AppRoleManager(new RoleStore<AppRole>(context));
string roleName = "Administrators";
string userName = "Admin";
string password = "Admin#2016";
string email = "admin#admin.com";
if (roleManager.RoleExists(roleName))
{
roleManager.Create(new AppRole(roleName));
}
AppUser user = userManager.FindByName(userName);
if (user == null)
{
userManager.Create(new AppUser {UserName = userName, Email = email}, password);
user = userManager.FindByName(userName);
}
if (!userManager.IsInRole(user.Id, roleName))
userManager.AddToRole(user.Id, roleName);
But I am getting The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.
The statement has been terminated error while executing the following statement.
userManager.Create(new AppUser {UserName = userName, Email = email}, password);
There is a field in IdentityUser base class called LockoutEndDateUtc it's type is DateTime? and in Database it is being correctly modeled as nullable.
I have seen other posts which instructs me to populate the date fields. But as it is nullable why should I populate it?
Yet I tried to initialize it like this:
LockoutEndDateUtc = DateTime.UtcNow.AddYears(3)
But still no luck.
I am not understanding why this error is popping up. Please help.
Thanks
I want to store globally the object User (that is the table USER in my db) in my HomeController, in that way i don't have to instantiate it in every single action.
I found the following solution that works pretty fine
public class HomeController : Controller
{
private DatabaseContext db = new DatabaseContext();
private User currentUser;
private User CurrentUser
{
get
{
if (User.Identity.IsAuthenticated)
//This function returns the object "User" (table USER in db) based on the PK of the table
currentUser = CustomDbFunctions.GetUserEntityFromUsername(User.Identity.Name, db);
return currentUser;
}
}
public ActionResult Index()
{
if (User.Identity.IsAuthenticated)
return View(CurrentUser);
else
return Redirect("login");
}
}
I'd like to know if there's a better (or more elegant) way to achieve the same goal.
Please note that i'm not using the MembershipProvider.
In your example the user object is instantiated in every single action (in contrast to what you said). This is because actions are usually invoked per http request and controller instances are disposed after each use.
Your code shares the instance structurally (you don't have to repeat the code) which is ok but what about sharing the code between different controllers? I'd suggest to refactor your GetUserEntityFromUsername a little bit so that you retrieve the object only once per request, using the Items container to get the request scope:
public class CustomDbFunctions
{
const string itemsUserKey = "_itemsUserKey";
public static User GetUserEntityFromUsername( IPrincipal principal, DatabaseContext db )
{
if ( principal == null || principal.Identity == null ||
!principal.Identity.IsAuthenticated
)
return null;
if ( HttpContext.Current.Items[itemsUserKey] == null )
{
// retrieve the data from the Db
var user = db.Users.FirstOrDefault( u => u.Name == User.Identity.Name );
HttpContext.Current.Items[itemsUserKey] = user;
}
return (User)HttpContext.Current.Items[itemsUserKey];
}
This way your wrapper takes care of retrieving the instance from the database once per request.
Note that this requires sharing your database context as entities should not be reused on different contexts. Fortunately, this can be done in a similar way:
public class CustomDbFunctions
{
const string dbUserKey = "dbUserKey";
public static DatabaseContext CurrentDatabaseContext
{
get
{
if ( HttpContext.Current.Items[dbUserKey] == null )
{
DatabaseContext ctx = new DatabaseContext(); // or any other way to create instance
HttpContext.Current.Items[dbUserKey] = ctx;
}
return (DatabaseContext)HttpContext.Current.Items[dbUserKey];
}
}
This way, the context instance, shared per request, is always available as
CustomDbFunctions.CurrentDatabaseContext