org.keycloak.models.GroupModel Java Examples

The following examples show how to use org.keycloak.models.GroupModel. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: GroupLDAPStorageMapper.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
    LDAPGroupMapperMode mode = config.getMode();

    // For now, import LDAP group mappings just during create
    if (mode == LDAPGroupMapperMode.IMPORT && isCreate) {

        List<LDAPObject> ldapGroups = getLDAPGroupMappings(ldapUser);

        // Import role mappings from LDAP into Keycloak DB
        for (LDAPObject ldapGroup : ldapGroups) {

            GroupModel kcGroup = findKcGroupOrSyncFromLDAP(realm, ldapGroup, user);
            if (kcGroup != null) {
                logger.debugf("User '%s' joins group '%s' during import from LDAP", user.getUsername(), kcGroup.getName());
                user.joinGroup(kcGroup);
            }
        }
    }
}
 
Example #2
Source File: GroupLDAPStorageMapper.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel kcGroup, int firstResult, int maxResults) {
    if (config.getMode() == LDAPGroupMapperMode.IMPORT) {
        // only results from Keycloak should be returned, or imported LDAP and KC items will duplicate
        return Collections.emptyList();
    }

    // TODO: with ranged search in AD we can improve the search using the specific range (not done for the moment)
    LDAPObject ldapGroup = loadLDAPGroupByName(kcGroup.getName());
    if (ldapGroup == null) {
        return Collections.emptyList();
    }

    MembershipType membershipType = config.getMembershipTypeLdapAttribute();
    return membershipType.getGroupMembers(realm, this, ldapGroup, firstResult, maxResults);
}
 
Example #3
Source File: JpaRealmProvider.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) {
    if (id == null) {
        id = KeycloakModelUtils.generateId();
    } else if (GroupEntity.TOP_PARENT_ID.equals(id)) {
        // maybe it's impossible but better ensure this doesn't happen
        throw new ModelException("The ID of the new group is equals to the tag used for top level groups");
    }
    GroupEntity groupEntity = new GroupEntity();
    groupEntity.setId(id);
    groupEntity.setName(name);
    RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId());
    groupEntity.setRealm(realmEntity.getId());
    groupEntity.setParentId(toParent == null? GroupEntity.TOP_PARENT_ID : toParent.getId());
    em.persist(groupEntity);
    em.flush();
    realmEntity.getGroups().add(groupEntity);

    GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
    return adapter;
}
 
Example #4
Source File: RoleContainerResource.java    From keycloak with Apache License 2.0 6 votes vote down vote up
/**
 * Return List of Groups that have the specified role name 
 *
 *
 * @param roleName
 * @param firstResult
 * @param maxResults
 * @param briefRepresentation if false, return a full representation of the GroupRepresentation objects
 * @return
 */
@Path("{role-name}/groups")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public  List<GroupRepresentation> getGroupsInRole(final @PathParam("role-name") String roleName, 
                                                @QueryParam("first") Integer firstResult,
                                                @QueryParam("max") Integer maxResults,
                                                @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) {
    
    auth.roles().requireView(roleContainer);
    firstResult = firstResult != null ? firstResult : 0;
    maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
    
    RoleModel role = roleContainer.getRole(roleName);
    
    if (role == null) {
        throw new NotFoundException("Could not find role");
    }
    
    List<GroupModel> groupsModel = session.realms().getGroupsByRole(realm, role, firstResult, maxResults);

    return groupsModel.stream()
    		.map(g -> ModelToRepresentation.toRepresentation(g, !briefRepresentation))
    		.collect(Collectors.toList());
}
 
Example #5
Source File: GroupLDAPStorageMapper.java    From keycloak with Apache License 2.0 6 votes vote down vote up
private void syncNonExistingGroup(RealmModel realm, Map.Entry<String, LDAPObject> groupEntry,
                                  SynchronizationResult syncResult, Set<String> visitedGroupIds, String groupName) {
    try {
        // Create each non-existing group to be synced in its own inner transaction to prevent race condition when
        // the group intended to be created was already created via other channel in the meantime
        KeycloakModelUtils.runJobInTransaction(ldapProvider.getSession().getKeycloakSessionFactory(), session -> {
            RealmModel innerTransactionRealm = session.realms().getRealm(realm.getId());
            GroupModel kcGroup = createKcGroup(innerTransactionRealm, groupName, null);
            updateAttributesOfKCGroup(kcGroup, groupEntry.getValue());
            syncResult.increaseAdded();
            visitedGroupIds.add(kcGroup.getId());
        });
    } catch (ModelException me) {
        logger.error(String.format("Failed to sync group %s from LDAP: ", groupName), me);
        syncResult.increaseFailed();
    }
}
 
Example #6
Source File: UserStorageManager.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
public List<UserModel> getGroupMembers(final RealmModel realm, final GroupModel group, int firstResult, int maxResults) {
    List<UserModel> results = query((provider, first, max) -> {
        if (provider instanceof UserQueryProvider) {
            return ((UserQueryProvider)provider).getGroupMembers(realm, group, first, max);

        } else if (provider instanceof UserFederatedStorageProvider) {
            List<String> ids = ((UserFederatedStorageProvider)provider).getMembership(realm, group, first, max);
            List<UserModel> rs = new LinkedList<UserModel>();
            for (String id : ids) {
                UserModel user = getUserById(id, realm);
                if (user != null) rs.add(user);
            }
            return rs;

        }
        return Collections.EMPTY_LIST;
    }, realm, firstResult, maxResults);
    return importValidation(realm, results);
}
 
Example #7
Source File: KeycloakModelUtils.java    From keycloak with Apache License 2.0 6 votes vote down vote up
private static GroupModel findSubGroup(String[] segments, int index, GroupModel parent) {
    for (GroupModel group : parent.getSubGroups()) {
        String groupName = group.getName();
        String[] pathSegments = formatPathSegments(segments, index, groupName);

        if (groupName.equals(pathSegments[index])) {
            if (pathSegments.length == index + 1) {
                return group;
            }
            else {
                if (index + 1 < pathSegments.length) {
                    GroupModel found = findSubGroup(pathSegments, index + 1, group);
                    if (found != null) return found;
                } else {
                    return null;
                }
            }

        }
    }
    return null;
}
 
Example #8
Source File: KeycloakModelUtils.java    From keycloak with Apache License 2.0 6 votes vote down vote up
public static Collection<String> resolveAttribute(UserModel user, String name, boolean aggregateAttrs) {
    List<String> values = user.getAttribute(name);
    Set<String> aggrValues = new HashSet<String>();
    if (!values.isEmpty()) {
        if (!aggregateAttrs) {
            return values;
        }
        aggrValues.addAll(values);
    }
    for (GroupModel group : user.getGroups()) {
        values = resolveAttribute(group, name);
        if (values != null && !values.isEmpty()) {
            if (!aggregateAttrs) {
                return values;
            }
            aggrValues.addAll(values);
        }
    }
    return aggrValues;
}
 
Example #9
Source File: JpaRealmProvider.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
public List<GroupModel> searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) {
    TypedQuery<String> query = em.createNamedQuery("getGroupIdsByNameContaining", String.class)
            .setParameter("realm", realm.getId())
            .setParameter("search", search);
    if(Objects.nonNull(first) && Objects.nonNull(max)) {
        query= query.setFirstResult(first).setMaxResults(max);
    }
    List<String> groups =  query.getResultList();
    if (Objects.isNull(groups)) return Collections.EMPTY_LIST;
    List<GroupModel> list = new ArrayList<>();
    for (String id : groups) {
        GroupModel groupById = session.realms().getGroupById(id, realm);
        while(Objects.nonNull(groupById.getParentId())) {
            groupById = session.realms().getGroupById(groupById.getParentId(), realm);
        }
        if(!list.contains(groupById)) {
            list.add(groupById);
        }
    }
    list.sort(Comparator.comparing(GroupModel::getName));

    return Collections.unmodifiableList(list);
}
 
Example #10
Source File: GroupMembershipMapper.java    From keycloak with Apache License 2.0 6 votes vote down vote up
/**
 * Adds the group membership information to the {@link IDToken#otherClaims}.
 * @param token
 * @param mappingModel
 * @param userSession
 */
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {

    List<String> membership = new LinkedList<>();
    boolean fullPath = useFullPath(mappingModel);
    for (GroupModel group : userSession.getUser().getGroups()) {
        if (fullPath) {
            membership.add(ModelToRepresentation.buildGroupPath(group));
        } else {
            membership.add(group.getName());
        }
    }
    String protocolClaim = mappingModel.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);

    token.getOtherClaims().put(protocolClaim, membership);
}
 
Example #11
Source File: UserAdapter.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
public Set<GroupModel> getGroups() {
    if (updated != null) return updated.getGroups();
    Set<GroupModel> groups = new LinkedHashSet<>();
    for (String id : cached.getGroups(modelSupplier)) {
        GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
        if (groupModel == null) {
            // chance that role was removed, so just delete to persistence and get user invalidated
            getDelegateForUpdate();
            return updated.getGroups();
        }
        groups.add(groupModel);

    }
    return groups;
}
 
Example #12
Source File: UserMapStorage.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public Set<GroupModel> getGroups(RealmModel realm, String userId) {
    Set<String> set = userGroups.get(getUserIdInMap(realm, userId));
    if (set == null) {
        return Collections.EMPTY_SET;
    }
    return set.stream()
      .map(realm::getGroupById)
      .collect(Collectors.toSet());
}
 
Example #13
Source File: JpaRealmProvider.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
    GroupEntity groupEntity = em.find(GroupEntity.class, id);
    if (groupEntity == null) return null;
    if (!groupEntity.getRealm().equals(realm.getId())) return null;
    GroupAdapter adapter =  new GroupAdapter(realm, em, groupEntity);
    return adapter;
}
 
Example #14
Source File: GroupPermissions.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public Map<String, String> getPermissions(GroupModel group) {
    initialize(group);
    Map<String, String> scopes = new LinkedHashMap<>();
    scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(group).getId());
    scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(group).getId());
    scopes.put(VIEW_MEMBERS_SCOPE, viewMembersPermission(group).getId());
    scopes.put(MANAGE_MEMBERS_SCOPE, manageMembersPermission(group).getId());
    scopes.put(MANAGE_MEMBERSHIP_SCOPE, manageMembershipPermission(group).getId());
    return scopes;
}
 
Example #15
Source File: UserMapStorage.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public List<String> getMembership(RealmModel realm, GroupModel group, int firstResult, int max) {
    return userGroups.entrySet().stream()
      .filter(me -> me.getValue().contains(group.getId()))
      .map(Map.Entry::getKey)
      .filter(realmUser -> realmUser.startsWith(realm.getId()))
      .map(realmUser -> realmUser.substring(realmUser.indexOf("/") + 1))
      .skip(firstResult)
      .limit(max)
      .collect(Collectors.toList());
}
 
Example #16
Source File: GroupPermissions.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public boolean getGroupsWithViewPermission(GroupModel group) {
    if (root.users().canView() || root.users().canManage()) {
        return true;
    }

    if (!root.isAdminSameRealm()) {
        return false;
    }

    ResourceServer server = root.realmResourceServer();
    if (server == null) return false;

    return hasPermission(group, VIEW_MEMBERS_SCOPE, MANAGE_MEMBERS_SCOPE);
}
 
Example #17
Source File: UserResource.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@PUT
@Path("groups/{groupId}")
@NoCache
public void joinGroup(@PathParam("groupId") String groupId) {
    auth.users().requireManageGroupMembership(user);
    GroupModel group = session.realms().getGroupById(groupId, realm);
    if (group == null) {
        throw new NotFoundException("Group not found");
    }
    auth.groups().requireManageMembership(group);
    if (!user.isMemberOf(group)){
        user.joinGroup(group);
        adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(session.getContext().getUri()).success();
    }
}
 
Example #18
Source File: GroupPermissions.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public boolean canView(GroupModel group) {
    if (canView() || canManage()) {
        return true;
    }

    if (!root.isAdminSameRealm()) {
        return false;
    }

    return hasPermission(group, MgmtPermissions.VIEW_SCOPE, MgmtPermissions.MANAGE_SCOPE);
}
 
Example #19
Source File: CachedGroup.java    From keycloak with Apache License 2.0 5 votes vote down vote up
public Set<String> getRoleMappings(Supplier<GroupModel> group) {
    // it may happen that groups were not loaded before so we don't actually need to invalidate entries in the cache
    if (group == null) {
        return Collections.emptySet();
    }
    return roleMappings.get(group);
}
 
Example #20
Source File: GroupLDAPStorageMapper.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public void leaveGroup(GroupModel group) {
    try (LDAPQuery ldapQuery = createGroupQuery(true)) {
        LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
        Condition roleNameCondition = conditionsBuilder.equal(config.getGroupNameLdapAttribute(), group.getName());

        String membershipUserLdapAttrName = getMembershipUserLdapAttribute();
        String membershipUserAttr = LDAPUtils.getMemberValueOfChildObject(ldapUser, config.getMembershipTypeLdapAttribute(), membershipUserLdapAttrName);
        Condition membershipCondition = conditionsBuilder.equal(config.getMembershipLdapAttribute(), membershipUserAttr);

        ldapQuery.addWhereCondition(roleNameCondition).addWhereCondition(membershipCondition);
        LDAPObject ldapGroup = ldapQuery.getFirstResult();

        if (ldapGroup == null) {
            // Group mapping doesn't exist in LDAP. For LDAP_ONLY mode, we don't need to do anything. For READ_ONLY, delete it in local DB.
            if (config.getMode() == LDAPGroupMapperMode.READ_ONLY) {
                super.leaveGroup(group);
            }
        } else {
            // Group mappings exists in LDAP. For LDAP_ONLY mode, we can just delete it in LDAP. For READ_ONLY we can't delete it -> throw error
            if (config.getMode() == LDAPGroupMapperMode.READ_ONLY) {
                throw new ModelException("Not possible to delete LDAP group mappings as mapper mode is READ_ONLY");
            } else {
                // Delete ldap role mappings
                cachedLDAPGroupMappings = null;
                deleteGroupMappingInLDAP(ldapUser, ldapGroup);
            }
        }
    }
}
 
Example #21
Source File: GroupMovedEvent.java    From keycloak with Apache License 2.0 5 votes vote down vote up
public static GroupMovedEvent create(GroupModel group, GroupModel toParent, String realmId) {
    GroupMovedEvent event = new GroupMovedEvent();
    event.realmId = realmId;
    event.groupId = group.getId();
    event.oldParentId = group.getParentId();
    event.newParentId = toParent==null ? null : toParent.getId();
    return event;
}
 
Example #22
Source File: GroupAdapter.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public Set<GroupModel> getSubGroups() {
    TypedQuery<String> query = em.createNamedQuery("getGroupIdsByParent", String.class);
    query.setParameter("parent", group.getId());
    List<String> ids = query.getResultList();
    Set<GroupModel> set = new HashSet<>();
    for (String id : ids) {
        GroupModel subGroup = realm.getGroupById(id);
        if (subGroup == null) continue;
        set.add(subGroup);
    }
    return set;
}
 
Example #23
Source File: LDAPGroupMapperTest.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Test
public void test07_newUserDefaultGroupsImportModeTest() throws Exception {

    // Check user group memberships
    testingClient.server().run(session -> {
        LDAPTestContext ctx = LDAPTestContext.init(session);
        RealmModel appRealm = ctx.getRealm();

        ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
        LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.IMPORT.toString());
        appRealm.updateComponent(mapperModel);

        UserModel david = session.users().addUser(appRealm, "davidkeycloak");

        GroupModel defaultGroup11 =  KeycloakModelUtils.findGroupByPath(appRealm, "/defaultGroup1/defaultGroup11");
        Assert.assertNotNull(defaultGroup11);

        GroupModel defaultGroup12 =  KeycloakModelUtils.findGroupByPath(appRealm, "/defaultGroup1/defaultGroup12");
        Assert.assertNotNull(defaultGroup12);

        GroupModel group31 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group31");
        Assert.assertNotNull(group31);
        GroupModel group32 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group32");
        Assert.assertNotNull(group32);
        GroupModel group4 =  KeycloakModelUtils.findGroupByPath(appRealm, "/group4");
        Assert.assertNotNull(group4);

        Set<GroupModel> groups = david.getGroups();
        Assert.assertTrue(groups.contains(defaultGroup11));
        Assert.assertTrue(groups.contains(defaultGroup12));
        Assert.assertFalse(groups.contains(group31));
        Assert.assertFalse(groups.contains(group32));
        Assert.assertFalse(groups.contains(group4));

    });
}
 
Example #24
Source File: RoleUtils.java    From keycloak with Apache License 2.0 5 votes vote down vote up
/**
 *
 * @param groups
 * @param targetGroup
 * @return true if targetGroup is in groups (directly or indirectly via parent child relationship)
 */
public static boolean isMember(Set<GroupModel> groups, GroupModel targetGroup) {
    if (groups.contains(targetGroup)) return true;

    for (GroupModel mapping : groups) {
        GroupModel child = mapping;
        while(child.getParent() != null) {
            if (child.getParent().equals(targetGroup)) return true;
            child = child.getParent();
        }
    }
    return false;
}
 
Example #25
Source File: GroupsResource.java    From keycloak with Apache License 2.0 5 votes vote down vote up
/**
 * create or add a top level realm groupSet or create child.  This will update the group and set the parent if it exists.  Create it and set the parent
 * if the group doesn't exist.
 *
 * @param rep
 */
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response addTopLevelGroup(GroupRepresentation rep) {
    auth.groups().requireManage();

    GroupModel child;
    Response.ResponseBuilder builder = Response.status(204);
    try {
        if (rep.getId() != null) {
            child = realm.getGroupById(rep.getId());
            if (child == null) {
                throw new NotFoundException("Could not find child by id");
            }
            realm.moveGroup(child, null);
            adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri());
        } else {
            child = realm.createGroup(rep.getName());
            GroupResource.updateGroup(rep, child);
            URI uri = session.getContext().getUri().getAbsolutePathBuilder()
                    .path(child.getId()).build();
            builder.status(201).location(uri);

            rep.setId(child.getId());
            adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), child.getId());
        }
    } catch (ModelDuplicateException mde) {
        return ErrorResponse.exists("Top level group named '" + rep.getName() + "' already exists.");
    }

    adminEvent.representation(rep).success();
    return builder.build();
}
 
Example #26
Source File: LDAPGroupMapper2WaySyncTest.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Before
public void before() {
    testingClient.server().run(session -> {
        LDAPTestContext ctx = LDAPTestContext.init(session);
        RealmModel appRealm = ctx.getRealm();

        String descriptionAttrName = LDAPTestUtils.getGroupDescriptionLDAPAttrName(ctx.getLdapProvider());

        // Add group mapper
        LDAPTestUtils.addOrUpdateGroupMapper(appRealm, ctx.getLdapModel(), LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName);

        // Remove all LDAP groups
        LDAPTestUtils.removeAllLDAPGroups(session, appRealm, ctx.getLdapModel(), "groupsMapper");

        // Add some groups for testing into Keycloak
        removeAllModelGroups(appRealm);

        GroupModel group1 = appRealm.createGroup("group1");
        group1.setSingleAttribute(descriptionAttrName, "group1 - description1");

        GroupModel group11 = appRealm.createGroup("group11", group1);

        GroupModel group12 = appRealm.createGroup("group12", group1);
        group12.setSingleAttribute(descriptionAttrName, "group12 - description12");

        GroupModel group2 = appRealm.createGroup("group2");

    });
}
 
Example #27
Source File: AbstractUserAdapter.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public Set<GroupModel> getGroups() {
    Set<GroupModel> set = new HashSet<>();
    if (appendDefaultGroups()) set.addAll(realm.getDefaultGroups());
    set.addAll(getGroupsInternal());
    return set;
}
 
Example #28
Source File: RoleUtils.java    From keycloak with Apache License 2.0 5 votes vote down vote up
/**
 * Checks whether the {@code targetRole} is contained in any of the {@code groups} or their parents
 * (if requested)
 * @param groups
 * @param targetRole
 * @param checkParentGroup When {@code true}, also parent group is recursively checked for role
 * @return true if targetRole is in roles (directly or indirectly via composite role)
 */
public static boolean hasRoleFromGroup(Iterable<GroupModel> groups, RoleModel targetRole, boolean checkParentGroup) {
    if (groups == null) {
        return false;
    }

    return StreamSupport.stream(groups.spliterator(), false)
            .anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup));
}
 
Example #29
Source File: HardcodedLDAPGroupStorageMapperFactory.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
    String groupName = config.getConfig().getFirst(HardcodedLDAPGroupStorageMapper.GROUP);
    if (groupName == null) {
        throw new ComponentValidationException("Group can't be null");
    }
    GroupModel group = KeycloakModelUtils.findGroupByPath(realm, groupName);
    if (group == null) {
        throw new ComponentValidationException("There is no group corresponding to configured value");
    }
}
 
Example #30
Source File: RoleUtils.java    From keycloak with Apache License 2.0 5 votes vote down vote up
/**
 * Checks whether the {@code targetRole} is contained in the given group or its parents
 * (if requested)
 * @param group Group to check role for
 * @param targetRole
 * @param checkParentGroup When {@code true}, also parent group is recursively checked for role
 * @return true if targetRole is in roles (directly or indirectly via composite role)
 */
public static boolean hasRoleFromGroup(GroupModel group, RoleModel targetRole, boolean checkParentGroup) {
    if (group.hasRole(targetRole))
        return true;

    if (checkParentGroup) {
        GroupModel parent = group.getParent();
        return parent != null && hasRoleFromGroup(parent, targetRole, true);
    }

    return false;
}