////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
////

[[hierarchicalRoles]]
== Hierarchical Roles

Hierarchical roles are a convenient way to reduce clutter in your request mappings.

.Hierarchical Roles configuration options
[cols="30,30,40"]
|====================
| *Property* | *Default Value*  | *Meaning*

|roleHierarchy
|_none_
|Hierarchical role definition

|roleHierarchyEntryClassName
|_none_
|Domain class used to manage persistent role hierarchy entries
|====================

For example, if you have several types of "`admin`" roles that can be used to access a URL pattern and you do not use hierarchical roles, you need to specify all the admin roles:

[source,groovy]
----
package com.mycompany.myapp

import grails.plugin.springsecurity.annotation.Secured

class SomeController {

   @Secured(['ROLE_ADMIN', 'ROLE_FINANCE_ADMIN', 'ROLE_SUPERADMIN'])
   def someAction() {
      ...
   }
}
----

However, if you have a business rule that says `ROLE_FINANCE_ADMIN` implies being granted `ROLE_ADMIN`, and that `ROLE_SUPERADMIN` implies being granted `ROLE_FINANCE_ADMIN`, you can express that hierarchy as:

[source,groovy]
----
grails.plugin.springsecurity.roleHierarchy = '''
   ROLE_SUPERADMIN > ROLE_FINANCE_ADMIN
   ROLE_FINANCE_ADMIN > ROLE_ADMIN
'''
----

Then you can simplify your mappings by specifying only the roles that are required:

[source,groovy]
----
package com.mycompany.myapp

import grails.plugin.springsecurity.annotation.Secured

class SomeController {

   @Secured('ROLE_ADMIN')
   def someAction() {
      ...
   }
}
----

You can also reduce the number of granted roles in the database. Where previously you had to grant `ROLE_SUPERADMIN`, `ROLE_FINANCE_ADMIN`, and `ROLE_ADMIN`, now you only need to grant `ROLE_SUPERADMIN`.

=== Persistent role hierarchy

Specifying a static string in the `roleHierarchy` property will be sufficient for most applications, but you can also store the information in your database. This is particularly useful if you're also storing requestmaps in the database. To use persistant storage, run the <<s2-create-role-hierarchy-entry>> script. This will create the domain class and enable persistent storage by registering its name as the `roleHierarchyEntryClassName` setting in `grails-app/conf/application.groovy`.

For example, running

[source,bash]
----
./gradlew runCommand "-Pargs=s2-create-role-hierarchy-entry com.yourapp.RoleHierarchyEntry"
----

will generate this class in `grails-app/domain/com/yourapp/RoleHierarchyEntry.groovy`:

[source,groovy]
.`RoleHierarchyEntry.groovy`
----
package com.yourapp

import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString

@EqualsAndHashCode(includes='entry')
@ToString(includes='entry', includeNames=true, includePackage=false)
class RoleHierarchyEntry implements Serializable {

   private static final long serialVersionUID = 1

   String entry

   static constraints = {
      entry blank: false, unique: true
   }

   static mapping = {
      cache true
   }
}
----

To store the equivalent entries for the ROLE_SUPERADMIN / ROLE_FINANCE_ADMIN / ROLE_ADMIN hierarchy, add code like this to a method in a transactional service:

[source,groovy]
.Listing {counter:listing}. Persisting `RoleHierarchyEntry` instances
----
if (!RoleHierarchyEntry.count()) {
   new RoleHierarchyEntry(entry: 'ROLE_SUPERADMIN > ROLE_FINANCE_ADMIN').save()
   new RoleHierarchyEntry(entry: 'ROLE_FINANCE_ADMIN > ROLE_ADMIN').save()
}
----

Remember to update the `roleHierarchy` beans `hierarchy` definition by calling `SpringSecurityService#reloadDBRoleHierarchy`, or your model changes are not reflected in the running application.
