diff --git a/client/src/locale/en.js b/client/src/locale/en.js
index 8dd44fa1..19fdb918 100644
--- a/client/src/locale/en.js
+++ b/client/src/locale/en.js
@@ -131,6 +131,7 @@ const en = {
roles: {
title: "Access Roles",
applicationName: "Application",
+ auditable: "Role {{name}} was created by {{createdBy}} at {{createdAt}}",
roleDetails: "Role details",
invitationDetails: "Invitation details",
applicationDetails: "Application(s) this role applies to",
diff --git a/client/src/locale/nl.js b/client/src/locale/nl.js
index 0b04e67f..dfa79743 100644
--- a/client/src/locale/nl.js
+++ b/client/src/locale/nl.js
@@ -131,6 +131,7 @@ const nl = {
roles: {
title: "Toegangsrollen",
applicationName: "Applicatie",
+ auditable: "Rol {{name}} is aangemaakt door {{createdBy}} op {{createdAt}}",
roleDetails: "Details rol",
invitationDetails: "Details uitnodiging",
applicationDetails: "Applicatie(s) voor deze rol",
diff --git a/client/src/pages/RoleForm.js b/client/src/pages/RoleForm.js
index 58a27895..d218a4ba 100644
--- a/client/src/pages/RoleForm.js
+++ b/client/src/pages/RoleForm.js
@@ -60,12 +60,13 @@ export const RoleForm = () => {
const [deletedUserRoles, setDeletedUserRoles] = useState(null);
useEffect(() => {
- if (!isUserAllowed(AUTHORITIES.MANAGER, user)) {
+ const newRole = id === "new";
+ //Managers may edit - certain attributes - of roles, but are not allowed to create new ones
+ if (!isUserAllowed(AUTHORITIES.MANAGER, user) || (newRole && !isUserAllowed(AUTHORITIES.INSTITUTION_ADMIN, user))) {
navigate("/404");
return;
}
setAllowedToEditApplication(isUserAllowed(AUTHORITIES.INSTITUTION_ADMIN, user))
- const newRole = id === "new";
const promises = [];
if (!newRole) {
promises.push(roleByID(parseInt(id, 10)));
@@ -269,6 +270,19 @@ export const RoleForm = () => {
{I18n.t("roles.roleDetails")}
+
+ {(!isNewRole && user.superUser) &&
+
+
+ {I18n.t("roles.auditable", {
+ name: role.name,
+ createdBy: role.auditable.createdBy,
+ createdAt: dateFromEpoch(role.auditable.createdAt)
+ })}
+
+
+ }
+
> rolesByApplication(@Parameter(hidden = true) U
return ResponseEntity.ok(manage.addManageMetaData(roleRepository.findAll()));
}
UserPermissions.assertAuthority(user, Authority.INSTITUTION_ADMIN);
+
+ if (limitInstitutionAdminRoleVisibility) {
+ List roles = roleRepository.findByOrganizationGUID(user.getOrganizationGUID());
+ return ResponseEntity.ok(manage.addManageMetaData(roles));
+ }
+
Set manageIdentifiers = new HashSet<>();
if (user.isInstitutionAdmin()) {
Set applicationManageIdentifiers = user.getApplications().stream().map(m -> (String) m.get("id")).collect(Collectors.toSet());
@@ -97,7 +107,7 @@ public ResponseEntity> rolesByApplication(@Parameter(hidden = true) U
manageIdentifiers.addAll(roleManageIdentifiers);
List roles = new ArrayList<>();
- //TODO feature toggle see application.yml feature.enforce-institution-admin-role-visibility
+ //TODO feature toggle see application.yml feature.limit-institution-admin-role-visibility
//findByOrganizationGUID_ApplicationUsagesApplicationManageId
manageIdentifiers.forEach(manageId -> roles.addAll(roleRepository.findByApplicationUsagesApplicationManageId(manageId)));
return ResponseEntity.ok(manage.addManageMetaData(roles));
@@ -152,7 +162,7 @@ public ResponseEntity newRole(@Validated @RequestBody Role role,
if (user != null) {
//OpenID connect login with User
UserPermissions.assertAuthority(user, Authority.INSTITUTION_ADMIN);
- //For super_users this is NULL, which is ok
+ //For super_users this is NULL, which is ok (unless they are impersonating an institution_admin)
role.setOrganizationGUID(user.getOrganizationGUID());
} else {
//API user with Basic Authentication
@@ -193,6 +203,9 @@ public ResponseEntity deleteRole(@PathVariable("id") Long id, @Parameter(h
manage.addManageMetaData(List.of(role));
UserPermissions.assertRoleAccess(user, role, Authority.INSTITUTION_ADMIN);
+ if (limitInstitutionAdminRoleVisibility && !user.getOrganizationGUID().equals(role.getOrganizationGUID())) {
+ throw new UserRestrictionException();
+ }
provisioningService.deleteGroupRequest(role);
roleRepository.delete(role);
diff --git a/server/src/main/java/access/manage/Manage.java b/server/src/main/java/access/manage/Manage.java
index 11397c51..7f9709a5 100644
--- a/server/src/main/java/access/manage/Manage.java
+++ b/server/src/main/java/access/manage/Manage.java
@@ -2,6 +2,7 @@
import access.model.GroupedProviders;
import access.model.Role;
+import access.model.User;
import org.springframework.util.CollectionUtils;
import java.util.*;
diff --git a/server/src/main/java/access/repository/RoleRepository.java b/server/src/main/java/access/repository/RoleRepository.java
index 30ab8e6b..41dc1eca 100644
--- a/server/src/main/java/access/repository/RoleRepository.java
+++ b/server/src/main/java/access/repository/RoleRepository.java
@@ -17,7 +17,7 @@ public interface RoleRepository extends JpaRepository {
List findByApplicationUsagesApplicationManageId(String manageId);
- List findByOrganizationGUID_ApplicationUsagesApplicationManageId(String organizationGUID, String manageId);
+ List findByOrganizationGUID(String organizationGUID);
List findByName(String name);
diff --git a/server/src/main/java/access/security/CustomOidcUserService.java b/server/src/main/java/access/security/CustomOidcUserService.java
index d7dea099..94825af2 100644
--- a/server/src/main/java/access/security/CustomOidcUserService.java
+++ b/server/src/main/java/access/security/CustomOidcUserService.java
@@ -75,7 +75,7 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio
try {
provisioningService.updateUserRequest(user);
} catch (RuntimeException e) {
- //We choose to ignore these
+ //We choose to ignore these, because remote provisioning errors may not prevent login flow
LOG.error("Error in updateUserRequest", e);
}
diff --git a/server/src/main/java/access/security/UserHandlerMethodArgumentResolver.java b/server/src/main/java/access/security/UserHandlerMethodArgumentResolver.java
index 91738444..ac59b1b8 100755
--- a/server/src/main/java/access/security/UserHandlerMethodArgumentResolver.java
+++ b/server/src/main/java/access/security/UserHandlerMethodArgumentResolver.java
@@ -19,13 +19,12 @@
import org.springframework.web.method.support.ModelAndViewContainer;
import java.security.Principal;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
-import static access.security.InstitutionAdmin.INSTITUTION_ADMIN;
+import static access.security.InstitutionAdmin.*;
import static access.security.SecurityConfig.API_TOKEN_HEADER;
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@@ -81,8 +80,8 @@ public User resolveArgument(MethodParameter methodParameter,
//Does not make any difference security-wise which user we return
User user = apiUsers.get(0);
if (StringUtils.hasText(organizationGUID)) {
- //The overhead is justified for API usage
- enrichInstitutionAdmin(user, organizationGUID);
+ //The overhead is needed / justified for API usage as this are stateless
+ addInstitutionAdminAttributes(user, organizationGUID);
}
return user;
} else {
@@ -128,7 +127,7 @@ public User resolveArgument(MethodParameter methodParameter,
String organizationGUID = user.getOrganizationGUID();
if (validImpersonation.get()) {
//The overhead for retrieving data from manage is justified when super_user is impersonating institutionAdmin
- enrichInstitutionAdmin(user, organizationGUID);
+ addInstitutionAdminAttributes(user, organizationGUID);
} else {
user.updateRemoteAttributes(attributes);
}
@@ -138,11 +137,9 @@ public User resolveArgument(MethodParameter methodParameter,
}
- private void enrichInstitutionAdmin(User user, String organizationGUID) {
- Optional