I tried to add and change roles in jhipster. First I just tried to change one use case's role to admin from user. Then I tested it and user can add employee even if the roles is ROLE_ADMIN so it didn't change anything.
I added new role as well, called MANAGER. I edited AuthoritiesConstants.java and added new role to JHI_AUTHORITY-table. Should I do something else or is this enough to get this working?
state('employee.new', {
parent: 'employee',
url: '/new',
data: {
roles: ['ROLE_ADMIN'],
},
onEnter: ['$stateParams', '$state', '$modal', function($stateParams, $state, $modal) {
$modal.open({
templateUrl: 'scripts/app/entities/employee/employee-dialog.html',
controller: 'EmployeeDialogController',
size: 'lg',
resolve: {
entity: function () {
return {nameFirst: null, nameLast: null, taxNumber: null, isFinnish: null, finnishSOTU: null, valtticard: null, birthDate: null, isContactPerson: null, isTiedonantaja: null, cOTARKENNE: null, id: null};
}
}
}).result.then(function(result) {
$state.go('employee', null, { reload: true });
}, function() {
$state.go('employee');
})
}]
})
Edit the following 6 files to include/exclude code specified in blocks to add/remove a role(ROLE_MANAGER as an example)
AuthoritiesConstants.java (constant to be used in java)
public static final String MANAGER = "ROLE_MANAGER";
src/main/resources/config/liquibase/authorities.csv (proper liquidbase update)
ROLE_MANAGER
src/main/resources/config/liquibase/users.csv (add username: manager with password: user)
5;manager;$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K;Manager;Manager;manager#localhost;true;en;system
src/main/resources/config/liquibase/users_authorities.csv (another proper liquidbase update)
5;ROLE_MANAGER
src/main/webapp/app/admin/user-management/user-management.controller.js (for role to be available in JavaScript)
$scope.authorities = ["ROLE_USER", "ROLE_ADMIN", "ROLE_MANAGER"];
src/main/webapp/app/admin/user-management/user-management-dialog.controller.js (for role to be available in JavaScript)
$scope.authorities = ["ROLE_USER", "ROLE_ADMIN", "ROLE_MANAGER"];
Restart the server once everything is in place and double check JHI_AUTHORITY and JHI_USER_AUTHORITY tables after application launch for a new ROLE_MANAGER to be there. Login into system with username: 'manager' and password: 'user'.
You must insert new role into JHI_AUTHORITY table then grant this role to some users in JHI_USER_AUTHORITY table. This means updating authorities.csv and users_authorities.csv file if you re-create your database (e.g. if you use H2).
On client-side, just add new role to roles property of your state definitions.
I have found an easiest way:
Disable liquibase from .gradle file (in my case App>gradle>profile_dev.gradle) by changing the following:
def profiles = 'dev,no-liquibase'
//if (project.hasProperty('no-liquibase')) {
// profiles += ',no-liquibase'
//}
Now change in src/main/webapp/scripts/app/admin/user-management/user-management.controller.js to add your role.
$scope.authorities = ["ROLE_USER", "ROLE_ADMIN", "YOUR_ROLE"];
And src/main/webapp/scripts/app/admin/user-management/user-management-dialog.controller.js
$scope.authorities = ["ROLE_USER", "ROLE_ADMIN", "YOUR_ROLE"];
Finally add "YOUR_ROLE" in "name" column of "jhi_authority" table in database and save. Now restart application and you will able to create user with your newly created role.
Taking a leaf out of #Constantin Zagorsky here are the steps that work.
AuthoritiesConstants.java (constant to be used in java)
public static final String MANAGER = "ROLE_MANAGER";
2.src/main/resources/config/liquibase/authorities.csv (proper liquibase update) [This will not run. But important to keep in sync with DB]
ROLE_MANAGER
Update DB [Important because liquibase will not pick up changes made in authorities,csv in step 2]
insert into jhi_authority values ('ROLE_MANAGER');
src/main/webapp/app/admin/user-management/user-management.controller.js(for role to be available in JavaScript)
$scope.authorities = ["ROLE_USER", "ROLE_ADMIN", "ROLE_MANAGER"];
src/main/webapp/app/admin/user-management/user-management-dialog.controller.js(for role to be available in JavaScript)
$scope.authorities = ["ROLE_USER", "ROLE_ADMIN", "ROLE_MANAGER"];
Modify public User createUser(ManagedUserVM managedUserVM) method in UserService.java (Very Important). Modify default password generation logic
// comment default password generation. In my case I made the default
//user as same as userid
//String encryptedPassword =
passwordEncoder.encode(RandomUtil.generatePassword());
String encryptedPassword = passwordEncoder.encode(managedUserVM.getLogin());
Log into application as Admin
Add new user with a new role. Default password would be same as username.
After above instruction I got:
ERROR [...]f.config.liquibase.AsyncSpringLiquibase : Liquibase could not start correctly, your database is NOT ready: Validation Failed:
[...]
To avoid that, run
./mvnw liquibase:clearCheckSums
User with new role, has no access to account settings, so you have to add new roles to
/src/main/webapp/app/account/password/password.state.js
/src/main/webapp/app/account/sessions/sessions.state.js
/src/main/webapp/app/account/settings/settings.state.js
Complete instruction:
https://codefitter2.blogspot.com/2016/11/how-to-create-new-role-in-jhipster.html
In JHipster 6, the csv file is named authority.csv. So change this file along the AuthoritiesConstants.java as described in previous posts.
Related
Having a few minor issues with role based authorization with dotnet core 2.2.3 and Keycloak 4.5.0.
In Keycloak, I've defined a role of 'tester' and a client role 'developer' with appropriate role mappings for an 'admin' user. After authenticating to Keycloak; if I look at the JWT in jwt.io, I can see the following:
{
"realm_access": {
"roles": [
"tester"
]
},
"resource_access": {
"template": {
"roles": [
"developer"
]
},
...
},
...
}
In .NET core, I've tried a bunch of things such as adding [Authorize(Roles = "tester")] or [Authorize(Roles = "developer")] to my controller method as well as using a policy based authorization where I check context.User.IsInRole("tester") inside my AuthorizationHandler<TRequirement> implementation.
If I set some breakpoints in the auth handler. When it gets hit, I can see the 'tester' and 'developer' roles listed as items under the context.user.Claims IEnumerable as follows.
{realm_access: {"roles":["tester"]}}
{resource_access: {"template":{"roles":["developer"]}}}
So I should be able to successfully do the authorization in the auth handler by verifying the values for realm_access and resource_access in the context.user.Claims collection, but this would require me to deserialize the claim values, which just seem to be JSON strings.
I'm thinking there has to be better way, or I'm not doing something quite right.
"AspNetCore.Authorization" expects roles in a claim (field) named "roles". And this claim must be an array of string (multivalued). You need to make some configuration on Keycloak side.
The 1st alternative:
You can change the existing role path.
Go to your Keycloak Admin Console > Client Scopes > roles > Mappers > client roles
Change "Token Claim Name" as "roles"
Multivalued: True
Add to access token: True
The 2nd alternative:
If you don't want to touch the existing path, you can create a new Mapper to show the same roles at the root as well.
Go to your Keycloak Admin Console > Client Scopes > roles > Mappers > create
Name: "root client roles" (or whatever you want)
Mapper Type: "User Client Role"
Multivalued: True
Token Claim Name: "roles"
Add to access token: True
The 4th alternative: read roles from JWT on ticket received event. Here is the the snipet:
options.Events.OnTicketReceived = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties!.GetTokens().ToList();
ClaimsIdentity claimsIdentity = (ClaimsIdentity) ctx.Principal!.Identity!;
foreach (AuthenticationToken t in tokens)
{
claimsIdentity.AddClaim(new Claim(t.Name, t.Value));
}
var access_token = claimsIdentity.FindFirst((claim) => claim.Type == "access_token")?.Value;
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(access_token);
JObject obj = JObject.Parse(jwtSecurityToken.Claims.First(c => c.Type == "resource_access").Value);
var roleAccess = obj.GetValue("your_client_id")!.ToObject<JObject>()!.GetValue("roles");
foreach (JToken role in roleAccess!)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role.ToString()));
}
return Task.CompletedTask;
};
A lot has been asked around the User.IsInRole, but I cannot find the right answer.
I need to validate a certain role, by using an AuthorizationHandler (through a authorizationrequirement)
I have a ASP.NET Core 2.1 project, whith Individual User Accounts. I have seeded the database and added the user to a (one role) role using userManager.AddToRoleAsync and, yes, the database shows the users, the roles and connection between them.
I have created a CandidateViewHandler that controls the authorization for a View-Contorller. IT looks as follows
public class CandidateViewHandler : AuthorizationHandler<ViewCandidateRequirement, Candidate>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewCandidateRequirement requirement, Candidate resource)
{
if (context.User.Identity.IsAuthenticated)
{
if (resource.Email == context.User.FindFirst(ClaimTypes.Name).Value)
{
context.Succeed(requirement);
}
else
{
bool IsAdmin = context.User.IsInRole("Administrator");
bool IsSearch = context.User.IsInRole("Searcher");
if (IsAdmin == true || IsSearch == true)
{
context.Succeed(requirement);
}
}
}
return Task.CompletedTask;
}
}
However, IsAdmin and IsSearch always return false. Even when testing it in the controller, the results remain the same. Should I use Claims in 2.1? And if so, how?
What the discussion, mentioned by Charles de M., IMO actually shows is that roles are in fact an unnecessary addition to identity. Seems to me that for the future it would be better to remove the AspNetRoles table (or at least not to use it anymore) and use roles as claims directly, instead of having identity to add the roles as claims.
Add your role type claims to the AspNetUserClaims table. The default role claim type is http://schemas.microsoft.com/ws/2008/06/identity/claims/role. Any claim of that type should automatically be mapped as role so it can be used with IsInRole.
You can also use custom mapping in the client:
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "role",
NameClaimType = "name",
};
});
A side note, you may consider to not use roles at all any more, as asp.net core has many, more advanced features which you can use for authorization.
Apparantly ASP.NET CORE 2.1 has problems dealing with roles: https://github.com/aspnet/Identity/issues/1813, as is explained in that Thread. The questions still remains on how to replace Roles with Claims though.
The workaround is to fallback to an older configuration in the startup file:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders()
(So remove AddDefaultIdentity<ApplicationUser>.AddRole<ApplicationRole> and replace with AddIdentity<AppplicationRole, ApplicationUser>. Now everything works fine.
Given a system with static permissions like posts.create, category.edit and such, and roles that can be created at runtime, both stored in a database but permissions can't (technically shouldn't) be modified; And the relations are N:M for users to roles and for roles to permissions:
Looking at the ACL package, at first sight it looks like I'd have to build up the ACL graph by querying my database roles on each request and adding them to the ACL instance and the allowed permissions like:
// Some class like AclService.php that should be called in Module.php
// ...
$roles = // query db for all roles and their permissions
foreach ($roles as $role) {
$acl->addRole($role->getName());
foreach ($role->getPermissions() as $permission) {
$acl->allow($role->getName(), null, $permission->getName());
}
}
Up to this point, in my controller's action (or a middleware, if they'd exist) I'd check if the user is allowed to execute the action:
// CategoryController
public function createAction() {
$user = // retrieve user from identity
if (! $acl->isAllowed($user->getRoles()->first(), null, 'categories.create')) {
// throw a 403 exception
}
}
What I don't get quite yet is, where does a Resource fits in this schema? Am I missing something here?
Or maybe are resources fit for when the permissions are not as granular as categories.create but just create?
You are using $acl->addRole($role->getName()); but as per the documentionation it should be defined in resources.
use Zend\Permissions\Acl\Acl;
use Zend\Permissions\Acl\Role\GenericRole as Role;
use Zend\Permissions\Acl\Resource\GenericResource as Resource;
$acl = new Acl();
$acl->addRole(new Role('guest'))
->addRole(new Role('member'))
->addRole(new Role('admin'));
$parents = array('guest', 'member', 'admin');
$acl->addRole(new Role('someUser'), $parents);
$acl->addResource(new Resource('someResource'));
$acl->deny('guest', 'someResource');
$acl->allow('member', 'someResource');
echo $acl->isAllowed('someUser', 'someResource') ? 'allowed' : 'denied';
If this doesn't help then let me know I will try to help.
I have "mydomain\myusername" in the database with the role Administrator. I have ran a couple of test with different configurations. The comment with the "+" is given access, whereas, the "-" is required to log on. It seems as though a user is given access if it's authorized by itself. But when a role is added in, the role takes priority and it doesn't even look at the single user.
How do I get it to work where it takes the single user or multiple users into account when a role is specified? I am using a custom [DefaultAuthorize]:Asp.net MVC4: Authorize on both controller and action and OverrideAuthorize so that the controller and the action permission don't AND together, but it doesn't cause the behavior where the Users is ignored over the Role. That behavior seems to be the default behavior of the authentication.
Edited: I just tested it some more, and the solution from the SO above doesn't really work to create the OR in controller/actions, it still requires a logon if both are specified but user is only in the controller group. It works if user is in the action group.
Edited: The only thing I see for sure is defining Roles in the Actions works as expected. Adding Roles or users in the controller create a nonsensical behavior.
So there are two issues that boggles the mind. 1. Can't seem to get rid of the AND condition when roles are specified in the controller and actions. 2. the user is ignored over role.
I am using MVC5 with windows authentication and roles.
//-[Authorize(Roles = "Publisher,Editor", Users = "mydomain\\myusername")] //this should have worked since myusername is running the test
//-[Authorize(Users = "mydomain\\myusername",Roles = "Publisher,Editor")] //this should have worked also
//+[Authorize(Users = "mydomain\\myusername", Roles = "Administrator,Publisher,Editor")] //this works because of Administrator
//+[Authorize(Users="mydomain\\myusername")]
//+[Authorize(Roles = "Administrator")]
//+[Authorize(Roles = "Administrator", Users = "mydomain\\myusername")]
//+[Authorize(Roles = "Administrator,Editor", Users = "mydomain\\myusername")]
//+[Authorize(Roles = "Publisher,Administrator,Editor", Users = "mydomain\\myusername")]
[DefaultAuthorize(Roles = "Publisher,Editor")]
public class PersonEntitiesController : Controller
{
//default role and override role works as long as it is in a group
//-[Authorize(Roles = "Administrator")] doesn't work as it's AND with controller
//when a user is grouped with a Role, the role takes priority
//doesn't work as myusername is ignored and only looks at Publisher but user is not in gorup
//-[OverrideAuthorize(Users = "mydomain\\myusername",Roles="Publisher")]
//+[OverrideAuthorize(Roles="Administrator")]
//+[OverrideAuthorize(Users = "mydomain\\myusername")] //works as long as myusername is listed by itself
//+[OverrideAuthorize(Users = "mydomain\\myusername",Roles="Administrator")] //the group works as long as myusername is in that group
public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page)
This is the default behavior of the Authorize attribute. The attribute verify that all the following rules pass:
The user is not null, it has an identity and it's authenticated.
If user's names were included, that the identity name is included on those.
If roles were included, that any of the roles included is present on the identity roles.
Doing an inspection of the ASP.NET MVC's AuthorizeAttribute.IsAuthorized code confirms it:
protected virtual bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
IPrincipal user = actionContext.ControllerContext.RequestContext.Principal;
if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
{
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
{
return false;
}
return true;
}
So as you suspected this behavior works as an AND, not an OR. If you want to have a different behavior, I recommend that you create a custom Authorization attribute and put your own logic on it. Just inherit from AuthorizeAttribute and override the IsAuthorized method with your custom logic.
I can create a secure, multi-tenant web app with Grails by :
setup spring security plugin,
setup Multi-tenant plugin (via multi-tenant install and multi-tenant-spring-security)
update config.groovy :
tenant {
mode = "multiTenant"
resolver.type = "springSecurity"
}
add : Integer userTenntId in User domain
add a domain class for tenant Organization
associate the tenants with Organization
Edit BootStrap.groovy.
Everything works fine in multi-tenant mode, but how to use mode = "singleTenant" ?
This configuration sound not working :
tenant {
mode = "singleTenant"
resolver.type = "springSecurity"
}
Edit :
I try this config :
tenant {
mode = "singleTenant"
resolver.type = "springSecurity"
datasourceResolver.type = "config"
dataSourceTenantMap {
t1 = "jdbc:hsqldb:file:custFoo"
t2 = "jdbc:hsqldb:file:custBar"
}
}
But I get :
ERROR errors.GrailsExceptionResolver - Executing action [list] of controller [org.example.TicketController] caused exception: java.lang.StackOverflowError
and :
Caused by: java.lang.StackOverflowError
at org.grails.multitenant.springsecurity.SpringSecurityCurrentTenant.getTenantIdFromSpringSecurity(SpringSecurityCurrentTenant.groovy:50)
at org.grails.multitenant.springsecurity.SpringSecurityCurrentTenant.this$2$getTenantIdFromSpringSecurity(SpringSecurityCurrentTenant.groovy)
at org.grails.multitenant.springsecurity.SpringSecurityCurrentTenant$this$2$getTenantIdFromSpringSecurity.callCurrent(Unknown Source)
at org.grails.multitenant.springsecurity.SpringSecurityCurrentTenant.get(SpringSecurityCurrentTenant.groovy:41)
at com.infusion.tenant.spring.TenantBeanContainer.getBean(TenantBeanContainer.java:53)
at com.infusion.tenant.spring.TenantMethodInterceptor.invoke(TenantMethodInterceptor.java:32)
at $Proxy14.getConnection(Unknown Source)
I'm still testing the multi tenant plugin (core) in singleTenant mode but it's already running altho not tested throughlly.
Here are the steps:
1- install spring security plugin, run grails s2-quickstart to set up the roles
2- Install multi-tenant-ehcache plugin and change your cache.provider_class in Datasource.groovy to:
cache.provider_class = 'grails.plugin.multitenant.ehcache.cache.MultiTenantEhCacheProvider'
3- Install multi tenant (core) plugin and set up the datasource resolver plus the dns resolver i did it in a static way. Plus altho in the plugin doc says we can configure the dataSoruceTenantMap like this:
dataSourceTenantMap {
t1 = "jdbc:mysql://localhost/ets_dev1"
t2 = "jdbc:mysql://localhost/ets_dev2"
}
didnt worked for me so i used the jndi option through grails.naming.entries in the config:
/** Config.groovy **/
grails.naming.entries = [
"jdbc/lh_dev1": [
type: "javax.sql.DataSource",
auth: "Container",
description: "My data source",
driverClassName: "com.mysql.jdbc.Driver",
url: "jdbc:mysql://localhost:3306/lh_dev1",
username: "xxx",
password: "xxx",
maxActive: "100",
maxIdle: "30",
maxWait: "10000"
],
"jdbc/lh_dev2": [
type: "javax.sql.DataSource",
auth: "Container",
description: "My data source",
driverClassName: "com.mysql.jdbc.Driver",
url: "jdbc:mysql://localhost:3306/lh_dev2",
username: "xxx",
password: "xxx",
maxActive: "100",
maxIdle: "30",
maxWait: "10000"
]
]
//MultiTenant plugin configuration
tenant{
mode = "singleTenant" // "singleTenant" OR "multiTenant"
datasourceResolver.type = "config" //This is the default and can be omitted
dataSourceTenantMap {
**t1 = "java:comp/env/jdbc/lh_dev1"****
**t2 = "java:comp/env/jdbc/lh_dev2"**
}
resolver.request.dns.type = "config" //This is the default
domainTenantMap {
lh_dev1 = 1
lh_dev2 = 2
}
}
At this point you will have 2 domains linked with 2 diferent databases once u click for example:
http://lh_dev1:8080/myProject --> it will open your project db
lh_dev1
http://lh_dev2:8080/myProject --> it will open your project db
lh_dev2
each tenant (client) has his own database.
4- Install multi-tenant-spring-security plugin and follow this steps(note i didnt implemented the security plugin yet once i do i will post the results):
In Config.groovy, your tenant resolver property should look like this:
tenant{
resolver.type = "springSecurity"
}
You will also need to add the following field to your Spring-Security User Domain Class
Integer userTenantId
Final Issues -->
Every time we fire up grails he will only do a create/update on the default database all the others will remain clean since i still dont know how to solve this problem so i simple exported the default database structure to all my other tenants database.
Finally i didnt do the #MultiTenant Annoation on the domains because i'm working with diferent database didnt saw the point of injecting the tenantid since every db has his own personal records.