Management for users, groups and roles: All necessary functions should now be implemented

Jens Pelzetter 2020-10-22 21:07:58 +02:00
parent f9f4507e35
commit 87f1d9a264
23 changed files with 1417 additions and 32 deletions

View File

@ -23,25 +23,17 @@ import org.libreccm.api.IdentifierParser;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.Group;
import org.libreccm.security.GroupManager;
import org.libreccm.security.GroupRepository;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.security.User;
import org.libreccm.security.UserRepository;
import org.libreccm.ui.admin.AdminMessages;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.mvc.MvcContext;
import javax.mvc.binding.BindingResult;
import javax.mvc.binding.MvcBinding;
import javax.transaction.Transactional;
@ -144,7 +136,7 @@ public class GroupFormController {
)
)
);
return "org/libreccm/ui/admin/users-groups-roles/group-form.xhtml";
return "org/libreccm/ui/admin/users-groups-roles/group-not-found.xhtml";
}
}

View File

@ -82,7 +82,7 @@ public class GroupMembersRolesController {
private UserRepository userRepository;
@POST
@Path("{groupIdentifier}/groups")
@Path("{groupIdentifier}/members")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)

View File

@ -23,13 +23,13 @@ package org.libreccm.ui.admin.usersgroupsroles;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class GroupUserFormEntry {
private long userId;
private String userUuid;
private String userName;
private boolean member;
public long getUserId() {
@ -63,8 +63,5 @@ public class GroupUserFormEntry {
public void setMember(final boolean member) {
this.member = member;
}
}

View File

@ -20,6 +20,8 @@ package org.libreccm.ui.admin.usersgroupsroles;
import org.libreccm.security.User;
import java.util.Objects;
/**
* Model friendly representation of a member of a group.
*
@ -42,7 +44,7 @@ public class GroupUserMembership implements Comparable<GroupUserMembership> {
public GroupUserMembership() {
// Nothing
}
public GroupUserMembership(final User user) {
userId = user.getPartyId();
userUuid = user.getUuid();
@ -51,7 +53,7 @@ public class GroupUserMembership implements Comparable<GroupUserMembership> {
givenName = user.getGivenName();
familyName = user.getFamilyName();
}
public long getUserId() {
return userId;
}
@ -102,7 +104,9 @@ public class GroupUserMembership implements Comparable<GroupUserMembership> {
@Override
public int compareTo(final GroupUserMembership other) {
int result = userName.compareTo(other.getUserName());
int result = userName.compareTo(
Objects.requireNonNull(other.getUserName())
);
if (result == 0) {
return primaryEmailAddress.compareTo(other.getPrimaryEmailAddress());
} else {

View File

@ -23,8 +23,12 @@ import org.libreccm.api.IdentifierParser;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.Group;
import org.libreccm.security.GroupManager;
import org.libreccm.security.GroupMembership;
import org.libreccm.security.GroupRepository;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.security.RoleManager;
import org.libreccm.security.RoleMembership;
import org.libreccm.ui.Message;
import org.libreccm.ui.MessageType;
import org.libreccm.ui.admin.AdminMessages;
@ -38,7 +42,9 @@ import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@ -57,6 +63,9 @@ public class GroupsController {
@Inject
private GroupDetailsModel groupDetailsModel;
@Inject
private GroupManager groupManager;
@Inject
private GroupRepository groupRepository;
@ -66,6 +75,9 @@ public class GroupsController {
@Inject
private Models models;
@Inject
private RoleManager roleManager;
@GET
@Path("/")
@AuthorizationRequired
@ -171,4 +183,68 @@ public class GroupsController {
}
}
@POST
@Path("/{groupIdentifier}/delete")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String deleteGroup(
@PathParam("groupIdentifier") final String groupIdentifierParam,
@FormParam("confirmed") final String confirmed
) {
if ("true".equals(confirmed)) {
final Identifier identifier = identifierParser.parseIdentifier(
groupIdentifierParam
);
final Optional<Group> result;
switch (identifier.getType()) {
case ID:
result = groupRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
case UUID:
result = groupRepository.findByUuid(identifier
.getIdentifier());
break;
default:
result = groupRepository.findByName(identifier
.getIdentifier());
break;
}
if (result.isPresent()) {
final Group group = result.get();
for (final RoleMembership roleMembership : group
.getRoleMemberships()) {
roleManager.removeRoleFromParty(
roleMembership.getRole(), group
);
}
for (final GroupMembership groupMembership : group
.getMemberships()) {
groupManager.removeMemberFromGroup(
groupMembership.getMember(), group
);
}
groupRepository.delete(result.get());
} else {
groupDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"usersgroupsroles.groups.not_found.message",
Arrays.asList(groupIdentifierParam)
),
MessageType.WARNING
)
);
return "org/libreccm/ui/admin/users-groups-roles/group-not-found.xhtml";
}
}
return "redirect:users-groups-roles/groups";
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.usersgroupsroles;
import org.libreccm.security.Party;
import org.libreccm.security.PartyRepository;
import org.libreccm.security.Role;
import org.libreccm.security.RoleMembership;
import org.libreccm.ui.Message;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.transaction.Transactional;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("RoleDetailsModel")
public class RoleDetailsModel {
@Inject
private PartyRepository partyRepository;
private long roleId;
private String uuid;
private String roleName;
private List<RolePartyMembership> members;
private List<RolePermission> permissions;
private final List<Message> messages;
public RoleDetailsModel() {
this.messages = new ArrayList<>();
}
public List<Message> getMessages() {
return Collections.unmodifiableList(messages);
}
public void addMessage(final Message message) {
messages.add(message);
}
public long getRoleId() {
return roleId;
}
public String getUuid() {
return uuid;
}
public String getRoleName() {
return roleName;
}
public List<RolePartyMembership> getMembers() {
return Collections.unmodifiableList(members);
}
public List<RolePermission> getPermissions() {
return Collections.unmodifiableList(permissions);
}
public List<RolePartyFormEntry> getRolePartyFormEnties() {
return partyRepository
.findAll()
.stream()
.map(this::buildRolePartyFormEntry)
.collect(Collectors.toList());
}
@Transactional(Transactional.TxType.REQUIRED)
protected void setRole(final Role role) {
Objects.requireNonNull(role);
roleId = role.getRoleId();
uuid = role.getUuid();
roleName = role.getName();
members = role
.getMemberships()
.stream()
.map(RoleMembership::getMember)
.map(RolePartyMembership::new)
.sorted()
.collect(Collectors.toList());
}
public boolean isNewRole() {
return roleId == 0;
}
private RolePartyFormEntry buildRolePartyFormEntry(final Party party) {
final RolePartyFormEntry entry = new RolePartyFormEntry();
entry.setPartyId(party.getPartyId());
entry.setPartyUuid(party.getUuid());
entry.setPartyName(party.getName());
entry.setMember(
members
.stream()
.anyMatch(
membership -> membership.getPartyUuid().equals(party
.getUuid())
)
);
return entry;
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.usersgroupsroles;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.security.Role;
import org.libreccm.security.RoleRepository;
import org.libreccm.ui.admin.AdminMessages;
import java.util.Arrays;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.mvc.binding.BindingResult;
import javax.mvc.binding.MvcBinding;
import javax.transaction.Transactional;
import javax.validation.constraints.NotBlank;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Controller
@Path("/users-groups-roles/roles/")
@RequestScoped
public class RoleFormController {
@Inject
private AdminMessages adminMessages;
@Inject
private BindingResult bindingResult;
@Inject
private IdentifierParser identifierParser;
@Inject
private Models models;
@Inject
private RoleRepository roleRepository;
@MvcBinding
@FormParam("roleName")
@NotBlank
private String roleName;
@POST
@Path("/new")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String createRole() {
if (bindingResult.isFailed()) {
models.put("errors", bindingResult.getAllMessages());
return "org/libreccm/ui/admin/users-groups-roles/role-form.xhtml";
}
final Role role = new Role();
role.setName(roleName);
roleRepository.save(role);
return "redirect:users-groups-roles/roles";
}
@POST
@Path("{roleIdentifier}/edit")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String updateRole(
@PathParam("roleIdentifier") final String roleIdentifierParam
) {
if (bindingResult.isFailed()) {
models.put("errors", bindingResult.getAllMessages());
return "org/libreccm/ui/admin/users-groups-roles/role-form.xhtml";
}
final Identifier identifier = identifierParser.parseIdentifier(
roleIdentifierParam
);
final Optional<Role> result;
switch (identifier.getType()) {
case ID:
result = roleRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
case UUID:
result = roleRepository.findByUuid(identifier.getIdentifier());
break;
default:
result = roleRepository.findByName(identifier.getIdentifier());
break;
}
if (result.isPresent()) {
final Role role = result.get();
role.setName(roleName);
return "redirect:users-groups-roles/roles";
} else {
models.put(
"errors", Arrays.asList(
adminMessages.getMessage(
"usersgroupsroles.roles.not_found.message",
Arrays.asList(roleIdentifierParam)
)
)
);
return "org/libreccm/ui/admin/users-groups-roles/role-not-found.xhtml";
}
}
}

View File

@ -0,0 +1,177 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.usersgroupsroles;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.Party;
import org.libreccm.security.PartyRepository;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.security.Role;
import org.libreccm.security.RoleManager;
import org.libreccm.security.RoleRepository;
import org.libreccm.ui.admin.AdminMessages;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Controller
@Path("/users-groups-roles/roles/")
@RequestScoped
public class RoleMembersController {
@Inject
private AdminMessages adminMessages;
@Inject
private IdentifierParser identifierParser;
@Inject
private Models models;
@Inject
private PartyRepository partyRepository;
@Inject
private RoleManager roleManager;
@Inject
private RoleRepository roleRepository;
@POST
@Path("{roleIdentifier}/members")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String updateRoleMemberships(
@PathParam("roleIdentifier") final String roleIdentifierParam,
@FormParam("roleMembers") final String[] roleMembersParam
) {
final Identifier identifier = identifierParser.parseIdentifier(
roleIdentifierParam
);
final Optional<Role> result;
switch (identifier.getType()) {
case ID:
result = roleRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
case UUID:
result = roleRepository.findByUuid(
identifier.getIdentifier()
);
break;
default:
result = roleRepository.findByName(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Role role = result.get();
final List<String> memberNames = Arrays.asList(roleMembersParam);
// Check for new members
final List<String> newMemberNames = memberNames
.stream()
.filter(memberName -> !hasMember(role, memberName))
.collect(Collectors.toList());
// Check for removed members
final List<String> removedMemberNames = role
.getMemberships()
.stream()
.map(membership -> membership.getMember().getName())
.filter(memberName -> !memberNames.contains(memberName))
.collect(Collectors.toList());
for (final String newMemberName : newMemberNames) {
addNewMember(role, newMemberName);
}
for (final String removedMemberName : removedMemberNames) {
removeMember(role, removedMemberName);
}
return String.format(
"redirect:/users-groups-roles/roles/%s/details",
roleIdentifierParam
);
} else {
models.put(
"errors", Arrays.asList(
adminMessages.getMessage(
"usersgroupsroles.roles.not_found.message",
Arrays.asList(roleIdentifierParam)
)
)
);
return "org/libreccm/ui/admin/users-groups-roles/role-not-found.xhtml";
}
}
private boolean hasMember(final Role role, final String memberName) {
return role
.getMemberships()
.stream()
.map(membership -> membership.getMember().getName())
.anyMatch(name -> name.equals(memberName));
}
private void addNewMember(final Role role, final String newMemberName) {
final Optional<Party> result = partyRepository.findByName(
newMemberName
);
if (result.isPresent()) {
final Party party = result.get();
roleManager.assignRoleToParty(role, party);
}
}
private void removeMember(final Role role, final String removedMemberName) {
final Optional<Party> result = partyRepository.findByName(
removedMemberName
);
if (result.isPresent()) {
final Party party = result.get();
roleManager.removeRoleFromParty(role, party);
}
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.usersgroupsroles;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class RolePartyFormEntry {
private long partyId;
private String partyUuid;
private String partyName;
private boolean member;
public long getPartyId() {
return partyId;
}
public void setPartyId(final long partyId) {
this.partyId = partyId;
}
public String getPartyUuid() {
return partyUuid;
}
public void setPartyUuid(final String partyUuid) {
this.partyUuid = partyUuid;
}
public String getPartyName() {
return partyName;
}
public void setPartyName(final String partyName) {
this.partyName = partyName;
}
public boolean isMember() {
return member;
}
public void setMember(final boolean member) {
this.member = member;
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.usersgroupsroles;
import org.libreccm.security.Party;
import java.util.Objects;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class RolePartyMembership implements Comparable<RolePartyMembership>{
private long partyId;
private String partyUuid;
private String partyName;
public RolePartyMembership() {
// Nothing
}
public RolePartyMembership(final Party party) {
partyId = party.getPartyId();
partyUuid = party.getUuid();
partyName = party.getName();
}
public long getPartyId() {
return partyId;
}
public void setPartyId(final long partyId) {
this.partyId = partyId;
}
public String getPartyUuid() {
return partyUuid;
}
public void setPartyUuid(final String partyUuid) {
this.partyUuid = partyUuid;
}
public String getPartyName() {
return partyName;
}
public void setPartyName(final String partyName) {
this.partyName = partyName;
}
@Override
public int compareTo(final RolePartyMembership other) {
return partyName.compareTo(
Objects.requireNonNull(other).getPartyName()
);
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.usersgroupsroles;
import org.libreccm.security.Permission;
import java.util.Objects;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class RolePermission implements Comparable<RolePermission> {
private long permissionId;
private String permissionUuid;
private String grantedPrivilege;
private String objectName;
private boolean objectPermission;
public RolePermission() {
// Nothing
}
public RolePermission(final Permission permission) {
permissionId = permission.getPermissionId();
permissionUuid = permission.getUuid();
grantedPrivilege = permission.getGrantedPrivilege();
objectPermission = permission.getObject() != null;
if (objectPermission) {
objectName = permission.getObject().getDisplayName();
}
}
public long getPermissionId() {
return permissionId;
}
public void setPermissionId(final long permissionId) {
this.permissionId = permissionId;
}
public String getPermissionUuid() {
return permissionUuid;
}
public void setPermissionUuid(final String permissionUuid) {
this.permissionUuid = permissionUuid;
}
public String getGrantedPrivilege() {
return grantedPrivilege;
}
public void setGrantedPrivilege(final String grantedPrivilege) {
this.grantedPrivilege = grantedPrivilege;
}
public String getObjectName() {
return objectName;
}
public void setObjectName(final String objectName) {
this.objectName = objectName;
}
public boolean isObjectPermission() {
return objectPermission;
}
public void setObjectPermission(final boolean objectPermission) {
this.objectPermission = objectPermission;
}
@Override
public int compareTo(final RolePermission other) {
int result = Objects.compare(
grantedPrivilege,
Objects.requireNonNull(other).getGrantedPrivilege(),
(privilege1, privilege2) -> privilege1.compareTo(privilege2)
);
if (result == 0 && isObjectPermission()) {
return Objects.compare(
objectName,
Objects.requireNonNull(other).getObjectName(),
(name1, name2) -> name1.compareTo(name2)
);
} else {
return result;
}
}
}

View File

@ -18,14 +18,29 @@
*/
package org.libreccm.ui.admin.usersgroupsroles;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.security.Role;
import org.libreccm.security.RoleRepository;
import org.libreccm.ui.Message;
import org.libreccm.ui.MessageType;
import org.libreccm.ui.admin.AdminMessages;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@ -38,11 +53,29 @@ import javax.ws.rs.PathParam;
@Path("/users-groups-roles/roles")
public class RolesController {
@Inject
private AdminMessages adminMessages;
@Inject
private RoleDetailsModel rolesDetailsModel;
@Inject
private RoleRepository roleRepository;
@Inject
private IdentifierParser identifierParser;
@Inject
private Models models;
@GET
@Path("/")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
public String getRoles() {
final List<Role> roles = roleRepository.findAll();
models.put("roles", roles);
return "org/libreccm/ui/admin/users-groups-roles/roles.xhtml";
}
@ -54,7 +87,149 @@ public class RolesController {
public String getRoleDetails(
@PathParam("roleIdentifier") final String roleIdentifierParam
) {
throw new UnsupportedOperationException();
final Identifier identifier = identifierParser.parseIdentifier(
roleIdentifierParam
);
final Optional<Role> result;
switch (identifier.getType()) {
case ID:
result = roleRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
case UUID:
result = roleRepository.findByUuid(
identifier.getIdentifier()
);
break;
default:
result = roleRepository.findByName(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
rolesDetailsModel.setRole(result.get());
return "org/libreccm/ui/admin/users-groups-roles/role-details.xhtml";
} else {
rolesDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"usersgroupsroles.roles.not_found_message",
Arrays.asList(roleIdentifierParam)
),
MessageType.WARNING
)
);
return "org/libreccm/ui/admin/users-groups-roles/role-not-found.xhtml";
}
}
@GET
@Path("/new")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
public String newRole() {
return "org/libreccm/ui/admin/users-groups-roles/role-form.xhtml";
}
@GET
@Path("/{roleIdentifier}/edit")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String editRole(
@PathParam("roleIdentifier") final String roleIdentifierParam
) {
final Identifier identifier = identifierParser.parseIdentifier(
roleIdentifierParam
);
final Optional<Role> result;
switch (identifier.getType()) {
case ID:
result = roleRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
case UUID:
result = roleRepository.findByUuid(
identifier.getIdentifier()
);
break;
default:
result = roleRepository.findByName(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
rolesDetailsModel.setRole(result.get());
return "org/libreccm/ui/admin/users-groups-roles/role-form.xhtml";
} else {
rolesDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"usersgroupsroles.roles.not_found_message",
Arrays.asList(roleIdentifierParam)
),
MessageType.WARNING
)
);
return "org/libreccm/ui/admin/users-groups-roles/role-not-found.xhtml";
}
}
@POST
@Path("/{roleIdentifier}/delete")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String deleteRole(
@PathParam("roleIdentifier") final String roleIdentifierParam,
@FormParam("confirmed") final String confirmed
) {
if ("true".equals(confirmed)) {
final Identifier identifier = identifierParser.parseIdentifier(
roleIdentifierParam
);
final Optional<Role> result;
switch (identifier.getType()) {
case ID:
result = roleRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
case UUID:
result = roleRepository.findByUuid(
identifier.getIdentifier()
);
break;
default:
result = roleRepository.findByName(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
roleRepository.delete(result.get());
} else {
rolesDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"usersgroupsroles.roles.not_found_message",
Arrays.asList(roleIdentifierParam)
),
MessageType.WARNING
)
);
return "org/libreccm/ui/admin/users-groups-roles/role-not-found.xhtml";
}
}
return "redirect:users-groups-roles/roles";
}
}

View File

@ -205,7 +205,7 @@ public class UserFormController {
Arrays.asList(userIdentifierParam)
)
));
return "org/libreccm/ui/admin/users-groups-roles/user-form.xhtml";
return "org/libreccm/ui/admin/users-groups-roles/user-not-found.xhtml";
}
}

View File

@ -41,6 +41,8 @@ public class UsersGroupsRolesPage implements AdminPage {
classes.add(GroupFormController.class);
classes.add(GroupMembersRolesController.class);
classes.add(RolesController.class);
classes.add(RoleFormController.class);
classes.add(RoleMembersController.class);
classes.add(UsersController.class);
classes.add(UserFormController.class);
classes.add(UserGroupsRolesController.class);

View File

@ -21,12 +21,11 @@
</a>
</li>
<li class="breadcrumb-item">
<a href="#{mvn.uri('GroupsController#getGroupDetails', { 'groupIdentfier': group.name })}">
<a href="#{mvn.uri('GroupsController#getGroupDetails', { 'groupIdentfier': GroupDetailsModel.groupName })}">
#{GroupDetailsModel.groupName}
</a>
</li>
<li>
#{GroupDetailsModel.newGroup ? AdminMessages['usersgroupsroles.groups.breadcrumb.new'] : AdminMessages['usersgroupsroles.groups.breadcrumb.edit']}
</li>
</ui:define>

View File

@ -16,12 +16,12 @@
</a>
</li>
<li class="breadcrumb-item">
<a href="#{mvc.uri('UsersController#getUsers')}">
#{AdminMessages['usersgroupsroles.users.label']}
<a href="#{mvc.uri('UsersController#getGroups')}">
#{AdminMessages['usersgroupsroles.groups.label']}
</a>
</li>
<li class="breadcrumb-item">
#{AdminMessages['usersgroupsroles.users.not_found.title']}
#{AdminMessages['usersgroupsroles.groups.not_found.title']}
</li>
</ui:define>

View File

@ -83,7 +83,7 @@
id="confirm-delete-#{group.name}"
tabindex="-1">
<div class="modal-dialog">
<form action="#"
<form action="#{mvc.uri('GroupsController#deleteGroup', {'groupIdentifier': group.name })}"
class="modal-content"
method="post">
<div class="modal-header">

View File

@ -0,0 +1,149 @@
<!DOCTYPE html [<!ENTITY times '&#215;'>]>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:xlink="http://www.w3.org/1999/xlink">
<ui:composition template="/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles.xhtml">
<ui:param name="activePage" value="usersgroupsroles" />
<ui:param name="activePanel" value="roles" />
<ui:param name="title"
value="#{AdminMessages.getMessage('usersgroupsroles.roles.role_details.title', [RoleDetailsModel.roleName])}" />
<ui:define name="breadcrumb">
<li class="breadcrumb-item">
<a href="#{mvc.uri('UsersGroupsRolesController#getOverview')}">
#{AdminMessages['usersgroupsroles.label']}
</a>
</li>
<li class="breadcrumb-item">
<a href="#{mvc.uri('RolesController#getRoles')}">
#{AdminMessages['usersgroupsroles.roles.label']}
</a>
</li>
<li class="breadcrumb-item">
#{RoleDetailsModel.roleName}
</li>
</ui:define>
<ui:define name="panel">
<dl>
<div>
<dt>#{AdminMessages['usersgroupsroles.roles.role_details.roleId']}</dt>
<dd>#{RoleDetailsModel.roleName}</dd>
</div>
<div>
<dt>#{AdminMessages['usersgroupsroles.roles.role_details.uuid']}</dt>
<dd>#{RoleDetailsModel.uuid}</dd>
</div>
<div>
<dt>#{AdminMessages['usersgroupsroles.roles.role_details.name']}</dt>
<dd>#{RoleDetailsModel.roleName}</dd>
</div>
</dl>
<a class="btn btn-primary"
href="#{mvc.uri('RolesController#editRole', {'roleIdentifier': RoleDetailsModel.roleName })}">
<svg class="bi"
width="1em"
height="1em"
fill="currentColor">
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#pen" />
</svg>
<span>
#{AdminMessages['usersgroupsroles.roles.role_details.edit_role']}
</span>
</a>
<div class="d-flex mt-3 mb-1">
<h2 class="mr-2">
#{AdminMessages['usersgroupsroles.roles.role_details.members.heading']}
</h2>
<button class="btn btn-primary"
data-toggle="modal"
data-target="#role-members-dialog"
type="button">
<svg class="bi"
width="1em"
height="1em"
fill="currentColor">
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#pen" />
</svg>
<span>#{AdminMessages['usersgroupsroles.roles.role_details.members.edit']}</span>
</button>
<div aria-labelledby="role-members-dialog-title"
aria-hidden="true"
class="modal fade"
data-backdrop="static"
id="role-members-dialog"
tabindex="-1">
<div class="modal-dialog">
<form action="#{mvc.uri('RoleMembersController#updateRoleMemberships', {'roleIdentifier': RoleDetailsModel.roleName } )}"
class="modal-content"
method="post">
<div class="modal-header">
<h3 class="modal-title"
id="role-members-dialog-title">
#{AdminMessages['usersgroupsroles.roles.role_details.members.dialog.title']}
</h3>
<button aria-label="#{AdminMessages['usersgroupsroles.roles.role_details.members.dialog.close']}"
class="close"
data-dismiss="modal"
type="button">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<c:forEach items="#{RoleDetailsModel.rolePartyFormEnties}"
var="entry">
<div class="form-check form-check-inline">
<input class="form-check-input"
checked="#{entry.member ? 'checked' : ''}"
id="party-#{entry.partyName}"
name="roleMembers"
value="#{entry.partyName}"
type="checkbox" />
<label class="form-check-label"
for="party-#{entry.partyName}">
#{entry.partyName}
</label>
</div>
</c:forEach>
</div>
<div class="modal-footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button">
#{AdminMessages['usersgroupsroles.roles.role_details.members.dialog.close']}
</button>
<button class="btn btn-primary"
type="submit">
#{AdminMessages['usersgroupsroles.roles.role_details.members.dialog.save']}
</button>
</div>
</form>
</div>
</div>
</div>
<c:choose>
<c:when test="#{RoleDetailsModel.members.size() > 0}">
<ul class="list-group mt-1">
<c:forEach items="#{RoleDetailsModel.members}"
var="member">
<li class="list-group-item">
#{member.partyName}
</li>
</c:forEach>
</ul>
</c:when>
<c:otherwise>
<div class="alert alert-info" role="alert">
#{AdminMessages['usersgroupsroles.roles.role_details.members.none']}
</div>
</c:otherwise>
</c:choose>
</ui:define>
</ui:composition>
</html>

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles.xhtml">
<ui:param name="activePage" value="usersgroupsroles" />
<ui:param name="activePanel" value="users" />
<ui:param name="title"
value="#{RoleDetailsModel.newRole ? AdminMessages['usersgroupsroles.roles.create.title'] : AdminMessages.getMessage('usersgroupsroles.roles.edit.title', [RoleDetailsModel.roleName]) }" />
<ui:define name="breadcrumb">
<li class="breadcrumb-item">
<a href="#{mvc.uri('UsersGroupsRolesController#getOverview')}">
#{AdminMessages['usersgroupsroles.label']}
</a>
</li>
<li class="breadcrumb-item">
<a href="#{mvc.uri('GroupsController#getGroups')}">
#{AdminMessages['usersgroupsroles.roles.label']}
</a>
</li>
<li class="breadcrumb-item">
<a href="#{mvn.uri('GroupsController#getGroupDetails', { 'roleIdentfier': RoleDetailsModel.roleName })}">
#{RoleDetailsModel.roleName}
</a>
</li>
<li>
#{RoleDetailsModel.newRole ? AdminMessages['usersgroupsroles.roles.breadcrumb.new'] : AdminMessages['usersgroupsroles.roles.breadcrumb.edit']}
</li>
</ui:define>
<ui:define name="panel">
<c:forEach items="#{errors}" var="error">
<div class="alert alert-danger" role="alert">
#{error}
</div>
</c:forEach>
<form action="#{RoleDetailsModel.newRole ? mvc.uri('RoleFormController#createRole') : mvc.uri('RoleFormController#updateRole', {'roleIdentifier' : RoleDetailsModel.roleName })}"
method="post">
<div class="form-group">
<label for="rolename">
#{AdminMessages['usersgroupsroles.roles.form.rolename.label']}
</label>
<input aria-described-by="rolename-help"
class="form-control"
id="rolename"
name="roleName"
value="#{RoleDetailsModel.roleName}"
type="text" />
<small class="form-text text-muted"
id="rolename-help">
#{AdminMessages['usersgroupsroles.roles.form.rolename.help']}
</small>
</div>
<a class="btn btn-warning"
href="#{mvc.uri('RolesController#getRoles')}">
#{AdminMessages['usersgroupsroles.roles.form.buttons.cancel']}
</a>
<button class="btn btn-success" type="submit">
<c:choose>
<c:when test="#{RoleDetailsModel.newRole}">
#{AdminMessages['usersgroupsroles.roles.form.buttons.create']}
</c:when>
<c:otherwise>
#{AdminMessages['usersgroupsroles.roles.form.buttons.save']}
</c:otherwise>
</c:choose>
</button>
</form>
</ui:define>
</ui:composition>
</html>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles.xhtml">
<ui:param name="activePage" value="usersgroupsroles" />
<ui:param name="activePanel" value="groups" />
<ui:param name="title"
value="#{AdminMessages('usersgroupsroles.roles.not_found.title')}" />
<ui:define name="breadcrumb">
<li class="breadcrumb-item">
<a href="#{mvc.uri('UsersGroupsRolesController#getOverview')}">
#{AdminMessages['usersgroupsroles.label']}
</a>
</li>
<li class="breadcrumb-item">
<a href="#{mvc.uri('UsersController#getRoles')}">
#{AdminMessages['usersgroupsroles.roles.label']}
</a>
</li>
<li class="breadcrumb-item">
#{AdminMessages['usersgroupsroles.roles.not_found.title']}
</li>
</ui:define>
<ui:define name="panel">
<c:forEach items="#{UserDetailsModel.messages}" var="message">
<div class="alert alert-#{message.messageType}" role="alert">
#{message}
</div>
</c:forEach>
</ui:define>
</ui:composition>
</html>

View File

@ -1,8 +1,9 @@
<!DOCTYPE html>
<!DOCTYPE html [<!ENTITY times '&#215;'>]>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:xlink="http://www.w3.org/1999/xlink">
<ui:composition template="/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles.xhtml">
<ui:param name="activePage" value="usersgroupsroles" />
<ui:param name="activePanel" value="roles" />
@ -21,7 +22,107 @@
</ui:define>
<ui:define name="panel">
<p>Roles placeholder</p>
<div class="row mb-2">
<div class="col-sm-9">
</div>
<div class="col-sm-3 text-right">
<a class="btn btn-secondary"
href="#{mvc.uri('RolesController#newRole')}">
<svg class="bi"
width="1em"
height="1em"
fill="currentColor">
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#plus-circle" />
</svg>
<span>#{AdminMessages['usersgroupsroles.roles.add']}</span>
</a>
</div>
</div>
<table class="table table-hover groups-table">
<thead class="thead-light">
<tr>
<th>#{AdminMessages['usersgroupsroles.roles.table.headers.rolename']}</th>
<th class="text-center" colspan="2">#{AdminMessages['usersgroupsroles.roles.table.headers.actions']}</th>
</tr>
</thead>
<tbody>
<c:forEach items="#{roles}"
var="role">
<tr>
<td>#{role.name}</td>
<td class="text-center action-col">
<a class="btn btn-info"
href="#{mvc.uri('RolesController#getRoleDetails', { 'roleIdentifier': role.name })}">
<svg class="bi"
width="1em"
height="1em"
fill="currentColor">
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#eye" />
</svg>
<span>
#{AdminMessages['usersgroupsroles.roles.detailslink.label']}
</span>
</a>
</td>
<td class="text-center action-col">
<button class="btn btn-danger"
data-toggle="modal"
data-target="#confirm-delete-#{role.name}"
href="#">
<svg class="bi"
width="1em"
height="1em"
fill="currentColor">
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#x-circle" />
</svg>
<span>
#{AdminMessages['usersgroupsroles.roles.delete.button.label']}
</span>
</button>
<div class="modal"
id="confirm-delete-#{role.name}"
tabindex="-1">
<div class="modal-dialog">
<form action="#{mvc.uri('RolesController#deleteRole', { 'roleIdentifier': role.name })}"
class="modal-content"
method="post">
<div class="modal-header">
<h3 class="modal-title">
#{AdminMessages['usersgroupsroles.roles.delete.confirm.title']}
</h3>
<button aria-label="#{AdminMessages['usersgroupsroles.roles.delete.confirm.cancel']}"
class="close"
data-dismiss="modal"
type="button">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
#{AdminMessages.getMessage('usersgroupsroles.roles.delete.confirm.message', [role.name])}
<input name="confirmed"
type="hidden"
value="true" />
</div>
<div class="modal-footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button">
#{AdminMessages['usersgroupsroles.roles.delete.confirm.cancel']}
</button>
<button class="btn btn-danger"
type="submit">
#{AdminMessages['usersgroupsroles.roles.delete.confirm.yes']}
</button>
</div>
</form>
</div>
</div>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</ui:define>
</ui:composition>
</html>

View File

@ -169,3 +169,33 @@ usersgroupsroles.groups.not_found.title=Group not found
usersgroupsroles.groups.group_details.groupId=Group ID
usersgroupsroles.groups.group_details.groupName=Group Name
usersgroupsroles.users.user_details.additional_email_addresses.cols.address=Address
usersgroupsroles.roles.not_found_message=Role {0} not found
usersgroupsroles.roles.not_found.title=Role not found
usersgroupsroles.roles.role_details.title=Details Role {0}
usersgroupsroles.roles.role_details.roleId=Role ID
usersgroupsroles.roles.role_details.name=Name
usersgroupsroles.roles.role_details.edit_role=Edit role
usersgroupsroles.roles.role_details.members.heading=Members
usersgroupsroles.roles.role_details.members.edit=Add/remove members
usersgroupsroles.roles.role_details.members.dialog.title=Add/remove members
usersgroupsroles.roles.role_details.members.dialog.close=Cancel
usersgroupsroles.roles.role_details.members.dialog.save=Apply
usersgroupsroles.roles.role_details.members.none=This role has no members
usersgroupsroles.roles.create.title=Create new role
usersgroupsroles.roles.edit.title=Edit role {0}
usersgroupsroles.roles.breadcrumb.new=New Role
usersgroupsroles.roles.breadcrumb.edit=Edit Role
usersgroupsroles.roles.form.rolename.label=Name
usersgroupsroles.roles.form.rolename.help=Unique name of the role
usersgroupsroles.roles.form.buttons.create=Create Role
usersgroupsroles.roles.form.buttons.save=Save
usersgroupsroles.roles.add=Add role
usersgroupsroles.roles.table.headers.rolename=Role
usersgroupsroles.roles.table.headers.actions=Actions
usersgroupsroles.roles.detailslink.label=Details
usersgroupsroles.roles.delete.button.label=Delete
usersgroupsroles.roles.delete.confirm.title=Are you sure to delele this role?
usersgroupsroles.roles.delete.confirm.message=Are you sure to delete role {0}?
usersgroupsroles.roles.delete.confirm.cancel=Cancel
usersgroupsroles.roles.delete.confirm.yes=Delete role
usersgroupsroles.roles.role_details.uuid=UUID

View File

@ -169,3 +169,33 @@ usersgroupsroles.groups.not_found.title=Gruppe nicht gefunden
usersgroupsroles.groups.group_details.groupId=ID der Gruppe
usersgroupsroles.groups.group_details.groupName=Name der Gruppe
usersgroupsroles.users.user_details.additional_email_addresses.cols.address=Adresse
usersgroupsroles.roles.not_found_message=Rolle {0} nicht verf\u00fcgbar
usersgroupsroles.roles.not_found.title=Rolle nicht gefunden
usersgroupsroles.roles.role_details.title=Details Rolle {0}
usersgroupsroles.roles.role_details.roleId=Rolle ID
usersgroupsroles.roles.role_details.name=Name
usersgroupsroles.roles.role_details.edit_role=Rolle bearbeiten
usersgroupsroles.roles.role_details.members.heading=Mitglieder
usersgroupsroles.roles.role_details.members.edit=Mitglieder hinzuf\u00fcgen/entfernen
usersgroupsroles.roles.role_details.members.dialog.title=Mitglieder hinzuf\u00fcgen/entfernen
usersgroupsroles.roles.role_details.members.dialog.close=Abbrechen
usersgroupsroles.roles.role_details.members.dialog.save=Anwenden
usersgroupsroles.roles.role_details.members.none=Diese Rolle hat keine Mitglieder
usersgroupsroles.roles.create.title=Neue Rolle anlegen
usersgroupsroles.roles.edit.title=Rolle {0} bearbeiten
usersgroupsroles.roles.breadcrumb.new=Neue Rolle
usersgroupsroles.roles.breadcrumb.edit=Rolle bearbeiten
usersgroupsroles.roles.form.rolename.label=Name
usersgroupsroles.roles.form.rolename.help=Eindeutiger Name der Rolle
usersgroupsroles.roles.form.buttons.create=Rolle anlegen
usersgroupsroles.roles.form.buttons.save=Speichern
usersgroupsroles.roles.add=Rolle hinzuf\u00fcgen
usersgroupsroles.roles.table.headers.rolename=Rolle
usersgroupsroles.roles.table.headers.actions=Aktionen
usersgroupsroles.roles.detailslink.label=Details
usersgroupsroles.roles.delete.button.label=L\u00f6schen
usersgroupsroles.roles.delete.confirm.title=Sind Sie sicher, dass Sie die Rolle l\u00f6schen wollen?
usersgroupsroles.roles.delete.confirm.message=Sind Sie sicher, dass Sie die Rolle {0} l\u00f6schen wollen?
usersgroupsroles.roles.delete.confirm.cancel=Abbrechen
usersgroupsroles.roles.delete.confirm.yes=Rolle l\u00f6schen
usersgroupsroles.roles.role_details.uuid=UUID