I am writing a custom UserStorageProvider for Keycloak against a HTTP-Service which contains only user information but no role information.
I want to accomplish that all users from this service are assigned to a composite role which is already existent in keycloak. As you can see I am resolving the the aggregated roles for composite role manually which is not really keen.
Main problem is, that the roles I assign here in the following AbstractUserAdapter are visible in the admin console in Keycloak under "assigned roles" but not in the "effective roles". Consequently they are not mapped into my bearer token.
#Override
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
Set<RoleModel> result = new HashSet<>();
for (RoleModel role : app.getRoles()) {
if("my_keycloak_comp_role".equals(role.getName())) {
result.add(role);
if (role.isComposite()) {
result.addAll(role.getComposites());
}
}
}
return result;
}
i am trying to give default role as Internal/Subscriber to all users.
i made changes in we made changes in file /_system/config/apimgt/applicationdata/tenant-conf.json and added role such as to Internal/creator,Internal/everyone,apimrole
"Name": "apim:subscribe",
"Roles": "admin,Internal/creator,Internal/everyone,apimrole,Internal/subscriber"
it gives me below error
org.wso2.carbon.apimgt.api.APIManagementException: Error while adding the subscriber
laxman#gmail.com#carbon.super#carbon.super
any help appreciated
New user creation takes place in the WSO2 API Manager in two ways.
Through the management console of the API Manager
Self signup
In 1st way you can assign roles when creating users.
For self signed-up users there already exists a handler to assign Internal/subscriber role to the new users who are having Internal/selfsignup role.
To assign role: Internal/subscriber to new users or existing role not assigned users we have below two options:
Option 1
If you wish to assign subscriber role to existing role not assigned users using Management Console, then you can go to roles listing page there:
There is an option: Assign Users in Actions column in role list relevant to Internal/subscriber role.
It will list all the users who have not assigned Internal/subscriber role and there are several options to select many users at once and assign the role.
Option 2
You can write a custom user operation event listener and add it as OSGI bundle.
In this case you can refer this WSO2 IS doc and write a event listener extending AbstractIdentityUserOperationEventListener.
This sample code worked for me:
public class SampleEventListener extends AbstractIdentityUserOperationEventListener {
private static final String EVENT_LISTENER_TYPE = "org.wso2.carbon.user.core.listener.UserOperationEventListener";
private static final String SUBSCRIBER_ROLE = "Internal/subscriber";
#Override
public boolean doPreAddUser(String userName, Object credential, String[] roleList, Map<String, String> claims,
String profile, UserStoreManager userStoreManager) throws UserStoreException {
List<String> roles = new ArrayList<>(Arrays.asList(roleList));
if (!roles.isEmpty() && !roles.contains(SUBSCRIBER_ROLE)) {
userStoreManager.updateRoleListOfUser(userName, new String[]{}, new String[] { SUBSCRIBER_ROLE });
}
return true;
}
This will add Internal/subscriber role to each newly adding user, if the user doesn't have that role in the process of adding new user.
Here it has mentioned multiple interfaces with which you can implement User Store Listeners.
For OSGI bundle creation and deployment process you can find this sample GitHub project. You can copy the built jar file to the directory <APIM_HOME>/repository/components/dropins/ by following the steps have been mentioned there. (Since WSO2 API Manager is also using WSO2 IS components you can follow the same steps mentioned in README with the API Manger as well)
You can go through this blog post to get complete idea about OSGI bundling.
I have a client in keycloak for my awx(ansible tower) webpage.
I need only the users from one specific keycloak group to be able to log in through this client.
How can I forbid all other users(except from one particular group) from using this keycloak client?
I found a solution which does not require the scripts extension or any changes on the flow.
The key for this solution are the Client Scopes. An application which wants to to authorize a user needs a scope like email or uid, right? What if you only pass them to an application if a user is in a specific group?
In the following, my client application name is App1.
Solution:
Go to your client roles (realm -> Clients -> click App1 -> Roles)
Click 'Add Role' -> enter Name (e.g. 'access') -> click 'Save'
Go to Client Scopes (realm -> Client Scopes)
Click on the scope which is needed by your client application (e.g. 'email')
Assign Client Role 'access' in 'Scope' Tab by choosing client application 'App1' in Drop Down 'Client Roles'
Now, you won't be able to log into your client application App1 anymore, as the role 'access' is not assigned to any user or group. You can try.
Let's create a new group and assign the role and a user to it.
Create Group (realm -> Groups -> Click 'New' -> enter Name 'App1 Users' -> Click Save)
In the Group, choose 'Role Mappings', choose 'App1' in Client Roles drop down, and assign the role 'access'
Assign User to 'App1 Users' (realm -> Users -> Click on User -> Groups -> Select 'App1 Users -> Click Join)
Voila, the chosen user can log into App1.
On Keycloak admin console, go to Clients menu, select your client. On the client configuration page, set Authorization Enabled: On, click Save. A new Authorization tab should appear, go to it, then to the Policies tab underneath, click Create Policy and select Group-based policy. There, you can restrict access to specific groups, assuming you have defined your groups via the Groups menu already.
--EDIT 2019-11-08--
As mentioned in comments, Client Protocol must be set to openid-connect and Access Type must be set to confidential, in order to make the Authorization Enabled option visible.
Follow-up to Allan's answer: His approach is working (for me ;-) ), though I had some struggle on how to deploy it. This is how I did it:
Bundle script in a JAR file as documented here, deploy it by copying to standalone/deployments/ (see manual link)
Enable scripts: Start Keycloak with -Dkeycloak.profile.feature.scripts=enabled
In your realm, create a new flow. Duplicate the Browser flow in a required subflow, and add the script authenticator as final (required) element:
Now add to all clients which should be restricted a client role feature:authenticate. Users which don't bear that role won't get access to the application.
If it can help, here is a script which helps implementing this behaviour for any client: if the client contains a given role (here it is called feature:authenticate), then the script checks whether the user has the role and shows an error page (a new template that needs to be deployed in the theme) if not.
AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationFlowError");
function authenticate(context) {
var MANDATORY_ROLE = 'feature:authenticate';
var username = user ? user.username : "anonymous";
var client = session.getContext().getClient();
LOG.debug("Checking access to authentication for client '" + client.getName() + "' through mandatory role '" + MANDATORY_ROLE + "' for user '" + username + "'");
var mandatoryRole = client.getRole(MANDATORY_ROLE);
if (mandatoryRole === null) {
LOG.debug("No mandatory role '" + MANDATORY_ROLE + "' for client '" + client.getName() + "'");
return context.success();
}
if (user.hasRole(mandatoryRole)) {
LOG.info("Successful authentication for user '" + username + "' with mandatory role '" + MANDATORY_ROLE + "' for client '" + client.getName() + "'");
return context.success();
}
LOG.info("Denied authentication for user '" + username + "' without mandatory role '" + MANDATORY_ROLE + "' for client '" + client.getName() + "'");
return denyAccess(context, mandatoryRole);
}
function denyAccess(context, mandatoryRole) {
var formBuilder = context.form();
var client = session.getContext().getClient();
var description = !mandatoryRole.getAttribute('deniedMessage').isEmpty() ? mandatoryRole.getAttribute('deniedMessage') : [''];
var form = formBuilder
.setAttribute('clientUrl', client.getRootUrl())
.setAttribute('clientName', client.getName())
.setAttribute('description', description[0])
.createForm('denied-auth.ftl');
return context.failure(AuthenticationFlowError.INVALID_USER, form);
}
I solved it like this:
Create a new role in Keycloak.
Assign this role to the group.
Create a new authentication script in Kycloak. Configure which role is allowed upon login (e.g. user.hasRole(realm.getRole("yourRoleName"))).
In the client's settings, under "Authentication Flow Overrides", choose the authentication script that was just created.
You can use this extension to restrict access to a specific group: https://github.com/thomasdarimont/keycloak-extension-playground/tree/master/auth-require-group-extension
according docu https://www.keycloak.org/docs/6.0/server_admin/#executions u have to active that feature to add some custom scripts with "add execution".
bin/standalone.sh|bat -Dkeycloak.profile.feature.scripts=enabled
#Allan solution with feature:authenticate looks good to me
I tried Allan's solution and it is working fine using Keycloak 11.0.3 but it has some cons mentioned below. Here is my solution for the authenticator script which does not grant access for users if they are not member at least one of the given groups. In such case a unique error message is shown.
AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationFlowError");
function authenticate(context) {
var allowed_groups = ['foo', 'bar'];
var username = user ? user.username : "anonymous";
var groups = user.getGroups();
var group_array = groups.toArray();
for (var i in group_array) {
var gn = group_array[i].getName();
if (allowed_groups.indexOf(gn) >= 0) {
LOG.info("Access granted for user '" + username + "' for being member of LDAP group '" + gn + "'");
return context.success();
}
}
LOG.info("Access denied for user '" + username + ". for not being member of any of the following LDAP groups: " + allowed_groups);
context.failure(AuthenticationFlowError.IDENTITY_PROVIDER_DISABLED, context.form().setError(
"User doesn't have the required LDAP group membership to view this page", null).createForm("error.ftl"));
return;
}
There are two minor user experience related cons with this solution worth mentioning:
When a not logged in user tries to connect to a client which access gets denied by the authenticator script the whole authentication flow is considered failure. This means the user doesn't get logged in into Keycloak despite the fact they provided the correct credentials
When a logged in user tries to connect to a client which access gets denied by the authenticator script the Keycloak login page is displayed (without showing any error message) which is deceptive as the user can have the false feeling they are not logged in
In addition if you maintain multiple clients and you need to have different groups (or roles) checked per client then you have to implement as many new authentication flows as many different checks you need. In short the solution works, but it has some disadvantages. I believe a simple feature such as restricting the access based on groups or roles is essential for an identity and access management system and should be supported natively!
2021 year - Keycloak 7.4.1.GA
I solved it like this for SAML2:
Add new Authentication flow (Just copy existing one)
Add execution "Group Access Observer" and set it as Required
Actions -> Config on Group Access Observer line
Fill group name
Go to your client and change Authentication flow to created now.
Best Regards
With Keycloak >= 13.x you may want to try the "Allow/Deny Access" authenticators with conditions. You can assign a role to a group and build the condition based on the role.
If that is not flexible enough, try out this library that I have build to solve exactly that issue.
I am using meteor-roles packages and statically it creates the user admin#example.com, normal#example.com,.....
NOw I have a signup pages where the user can sign up.Initially I create the account by following code in client side, i am using coffeescritp:
Accounts.createUser
email: email
password: password
By doing these I can create the new user. Now how do I assign the role to that newly created user. I have roles- admin, normal, manage-users.
When doing these
id = Meteor.userId()
roles = "admin"
Roles.addUsersToRoles id, roles
I get the error "Exception while delivering result of invoking 'createUser' Error {} Error: Missing 'users' param
How can I assign the role dynamically.
Thank you in advance.!!!
You can only use Meteor.userId() if the user ID you are assigning the role(s) too is the current user. You would need to call Meteor.userLoginWith*() before trying to use Roles.addUsersToRoles(Meteor.userId(), roles).
I would suggest creating the user, assigning the roles, and then logging them in. If you log the user in prior to assigning an appropriate role, role-specific routes, allow functions, and publish functions may not behave properly.
I would suggest creating the user:
var userId = Accounts.createUser({
username: 'username',
email: 'email#email.com',
password: 'passwordString',
profile: {}
});
And then adding the user to the appropriate role:
var roles = ['admin', 'roleName'];
Roles.addUsersToRoles(userId, roles);
Then finally logging them in, after the role(s) has been assigned.
Im having some problems creating new user from one of my controllers. I'm trying to add a new user to my MongoDB user collection like this. Authorities is defined as a Set of Role in the domain.
Role role = new Role(authority:"ROLE_USER")
User user = new User(username:params.username,email:params.email,password:params.password,enabled:params.enabled,
accountExpired:params.accountExpired,accountLocked:params.accountLocked,passwordExpired:params.passwordExpired,
authorities:[role])
if (user.validate()) {
user.save(flush:true)
} else {
user.errors.allErrors.each { println it }
}
The exact same code is able to create a user successfully from the bootstrap, but when i'm trying to do the same thing from a simple controller i'm getting this error:
2012-09-24 10:43:27,450 [http-8080-3] ERROR binding.GrailsDataBinder - Unable to auto-create type interface java.util.Set, class java.lang.InstantiationException thrown in constructor
a:662)
Looks like the problem is with data binding. You have to create User with authorities first and then add role using UserRole domain. Something like:
Role role = Role.findByAuthority("ROLE_USER")
User user = new User(username:params.username,email:params.email,password:params.password,enabled:params.enabled, accountExpired:params.accountExpired,accountLocked:params.accountLocked,passwordExpired:params.passwordExpired)
new UserRole(user: user, role: role).save(flush: flush, insert: true)
user.save(flush:true)
For more information how to create user with spring security, you may want to look at Spring Security UI