From 628d0d9f77fc3bda07617a6ba5815448c6ba8bd7 Mon Sep 17 00:00:00 2001 From: chas galey Date: Wed, 3 Jan 2024 09:33:52 -0700 Subject: [PATCH 01/19] propose migration of docker file to eclipse jdk --- Dockerfile | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e027f83cd..8581c0499 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,27 @@ -FROM openjdk:17.0.2-slim as build +FROM eclipse-temurin:17-ubi9-minimal as build COPY . /mms WORKDIR /mms + +RUN if [ -d "./certs" ]; then \ + mv certs/*.pem /etc/pki/ca-trust/source/anchors/ ; \ + /usr/bin/update-ca-trust extract ; \ + ln -sf /etc/pki/ca-trust/extracted/java/cacerts "$JAVA_HOME/lib/security/cacerts" ; \ + fi + +RUN microdnf install -y findutils + RUN ./gradlew --no-daemon bootJar --warning-mode all RUN find . -type f -name example-*.jar -not -iname '*javadoc*' -not -iname '*sources*' -exec cp '{}' '/app.jar' ';' + +FROM eclipse-temurin:17-ubi9-minimal + +WORKDIR /opt/mms +COPY --from=build /app.jar /opt/mms/app.jar +COPY --from=build /etc/pki/ca-trust /etc/pki/ + ENV JDK_JAVA_OPTIONS "-XX:MaxRAMPercentage=90.0" -ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "-jar", "/app.jar"] +ENV USE_SYSTEM_CA_CERTS "true" + +CMD ["java", "-Djdk.tls.client.protocols=TLSv1.2,TLSv1.3", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "-jar", "/opt/mms/app.jar"] EXPOSE 8080 From 2903cf421b1c3429b39819d62a54751df9b20b5b Mon Sep 17 00:00:00 2001 From: chas galey Date: Wed, 3 Jan 2024 09:34:37 -0700 Subject: [PATCH 02/19] add gitignore changes --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d9defa701..661bad695 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ application.properties localhost-env.json .vscode +certs/ ### STS ### .apt_generated From 6073b0495cf2fc6e40cff190759d1306441e6817 Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 10 Apr 2024 15:20:33 -0600 Subject: [PATCH 03/19] trying to refactor group permissions --- .gitignore | 1 + authenticator/README.rst | 2 +- .../PermissionUpdatesResponseBuilder.java | 2 +- .../core/config/AuthorizationConstants.java | 2 + .../openmbee/mms/core/config/Constants.java | 8 +- .../openmbee/mms/core/config/Privileges.java | 7 +- .../mms/core/dao/GroupPersistence.java | 1 + .../PermissionsDelegateFactory.java | 3 + .../objects/PermissionUpdateResponse.java | 13 +- .../core/security/MethodSecurityService.java | 32 ++ .../services/DefaultPermissionService.java | 103 ++++++- .../mms/core/services/PermissionService.java | 16 + .../core/utils/PermissionsDelegateUtil.java | 14 + .../org/openmbee/mms/data/dao/ProjectDAO.java | 2 - .../mms/data/domains/global/Group.java | 77 ++++- .../data/domains/global/GroupGroupPerm.java | 56 ++++ .../data/domains/global/GroupUserPerm.java | 56 ++++ .../mms/data/domains/global/User.java | 16 +- docker-compose.yml | 4 +- docs/modules/localuser.rst | 2 +- example/artifacts.postman_collection.json | 4 +- example/example.gradle | 3 +- example/groups.postman_collection.json | 161 ++++++++-- example/localauth.postman_collection.json | 12 +- example/permissions.postman_collection.json | 143 ++++++++- .../resources/application-test.properties | 2 + example/twc.postman_collection.json | 4 +- .../config/PermissionInit.java | 63 +++- .../dao/FederatedGroupPersistence.java | 10 + .../DefaultBranchPermissionsDelegate.java | 2 +- .../DefaultGroupPermissionsDelegate.java | 290 ++++++++++++++++++ .../DefaultOrgPermissionsDelegate.java | 2 +- .../DefaultProjectPermissionsDelegate.java | 2 +- ...ratedPermissionsUpdateResponseBuilder.java | 41 ++- .../mms/groups/constants/GroupConstants.java | 8 +- .../controllers/LocalGroupsController.java | 115 +++++-- .../groups/objects/GroupUsersResponse.java | 26 ++ .../mms/groups/objects/GroupsRequest.java | 21 ++ .../mms/groups/objects/GroupsResponse.java | 21 +- .../java/org/openmbee/mms/json/GroupJson.java | 5 +- ldap/ldap.gradle | 2 +- .../mms/ldap/{ => config}/LdapCondition.java | 2 +- .../ldap/{ => config}/LdapSecurityConfig.java | 125 +++----- .../security/LdapUsersDetailsService.java | 86 ++++++ {localuser => localauth}/README.rst | 4 +- localauth/localauth.gradle | 4 + .../localauth}/config/AuthProviderConfig.java | 12 +- .../config/LocalAuthSecurityConfig.java | 6 +- .../config/PasswordEncoderConfig.java | 2 +- .../config/UserPasswordRulesConfig.java | 2 +- .../security/LocalUsersDetailsService.java | 70 +++-- .../resources/application.properties.example | 0 .../controllers/LocalUserController.java | 89 ------ .../localuser/security/UserDetailsImpl.java | 74 ----- .../permissions/PermissionsController.java | 39 ++- .../PermissionsLookupController.java | 3 + .../DefaultPermissionsDelegateFactory.java | 42 +++ .../permissions/objects/PermissionLookup.java | 7 +- .../GroupGroupPermRepository.java | 30 ++ .../repositories/GroupUserPermRepository.java | 29 ++ .../search/objects/BasicSearchRequest.java | 1 + .../mms/twc/TeamworkCloudEndpoints.java | 1 + .../TwcPermissionsDelegateFactory.java | 11 + .../twc/security/TwcUserDetailsService.java | 40 ++- .../mms/twc/utilities/AdminUtils.java | 43 +++ .../openmbee/mms/twc/utilities/JsonUtils.java | 11 + .../openmbee/mms/twc/utilities/RestUtils.java | 26 ++ .../mms/twc/utilities/TwcPermissionUtils.java | 2 +- .../security/TwcAuthenticationFilterTest.java | 3 +- twc/twc.gradle | 2 + .../mms/users/controller/UsersController.java | 113 +++++++ .../mms/users/objects}/UserCreateRequest.java | 23 +- .../mms/users/objects}/UsersResponse.java | 2 +- .../security/AbstractUsersDetailsService.java | 64 ++++ .../users/security/DefaultUsersDetails.java | 13 +- .../mms/users/security/UsersDetails.java | 8 + .../users/security/UsersDetailsService.java | 22 ++ .../localuser.gradle => users/users.gradle | 0 78 files changed, 1917 insertions(+), 448 deletions(-) create mode 100644 data/src/main/java/org/openmbee/mms/data/domains/global/GroupGroupPerm.java create mode 100644 data/src/main/java/org/openmbee/mms/data/domains/global/GroupUserPerm.java create mode 100644 federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java create mode 100644 groups/src/main/java/org/openmbee/mms/groups/objects/GroupUsersResponse.java create mode 100644 groups/src/main/java/org/openmbee/mms/groups/objects/GroupsRequest.java rename ldap/src/main/java/org/openmbee/mms/ldap/{ => config}/LdapCondition.java (93%) rename ldap/src/main/java/org/openmbee/mms/ldap/{ => config}/LdapSecurityConfig.java (78%) create mode 100644 ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java rename {localuser => localauth}/README.rst (92%) create mode 100644 localauth/localauth.gradle rename {localuser/src/main/java/org/openmbee/mms/localuser => localauth/src/main/java/org/openmbee/mms/localauth}/config/AuthProviderConfig.java (85%) rename localuser/src/main/java/org/openmbee/mms/localuser/config/LocalUserSecurityConfig.java => localauth/src/main/java/org/openmbee/mms/localauth/config/LocalAuthSecurityConfig.java (88%) rename {localuser/src/main/java/org/openmbee/mms/localuser => localauth/src/main/java/org/openmbee/mms/localauth}/config/PasswordEncoderConfig.java (94%) rename {localuser/src/main/java/org/openmbee/mms/localuser => localauth/src/main/java/org/openmbee/mms/localauth}/config/UserPasswordRulesConfig.java (92%) rename localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java => localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java (58%) rename {localuser => localauth}/src/main/resources/application.properties.example (100%) delete mode 100644 localuser/src/main/java/org/openmbee/mms/localuser/controllers/LocalUserController.java delete mode 100644 localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsImpl.java create mode 100644 permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java create mode 100644 rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupGroupPermRepository.java create mode 100644 rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupUserPermRepository.java create mode 100644 twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java create mode 100644 users/src/main/java/org/openmbee/mms/users/controller/UsersController.java rename {localuser/src/main/java/org/openmbee/mms/localuser/security => users/src/main/java/org/openmbee/mms/users/objects}/UserCreateRequest.java (67%) rename {localuser/src/main/java/org/openmbee/mms/localuser/security => users/src/main/java/org/openmbee/mms/users/objects}/UsersResponse.java (87%) create mode 100644 users/src/main/java/org/openmbee/mms/users/security/AbstractUsersDetailsService.java rename twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetails.java => users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetails.java (88%) create mode 100644 users/src/main/java/org/openmbee/mms/users/security/UsersDetails.java create mode 100644 users/src/main/java/org/openmbee/mms/users/security/UsersDetailsService.java rename localuser/localuser.gradle => users/users.gradle (100%) diff --git a/.gitignore b/.gitignore index d9defa701..dab6e52ff 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ application.properties localhost-env.json .vscode +certs ### STS ### .apt_generated diff --git a/authenticator/README.rst b/authenticator/README.rst index c6a7421e3..eae74b1a7 100644 --- a/authenticator/README.rst +++ b/authenticator/README.rst @@ -18,5 +18,5 @@ References * `Overview of Spring Security `_ * `UserDetails `_ * `Authentication `_ -* :ref:`localuser` +* :ref:`localauth` * :ref:`ldap` \ No newline at end of file diff --git a/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdatesResponseBuilder.java b/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdatesResponseBuilder.java index 4ed15275a..a0b7cb979 100644 --- a/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdatesResponseBuilder.java +++ b/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdatesResponseBuilder.java @@ -38,7 +38,7 @@ public PermissionUpdatesResponseBuilder insertGroups(PermissionUpdateResponse pe return this; } - public PermissionUpdatesResponse getPermissionUpdatesReponse() { + public PermissionUpdatesResponse getPermissionUpdatesResponse() { PermissionUpdatesResponse permissionUpdatesResponse = new PermissionUpdatesResponse(); permissionUpdatesResponse.setInherit(this.inherit); permissionUpdatesResponse.setPublic(this.isPublic); diff --git a/core/src/main/java/org/openmbee/mms/core/config/AuthorizationConstants.java b/core/src/main/java/org/openmbee/mms/core/config/AuthorizationConstants.java index a96053d0b..43590d038 100644 --- a/core/src/main/java/org/openmbee/mms/core/config/AuthorizationConstants.java +++ b/core/src/main/java/org/openmbee/mms/core/config/AuthorizationConstants.java @@ -8,4 +8,6 @@ public class AuthorizationConstants { public static final String EVERYONE = "everyone"; public static final String ADMIN = "ADMIN"; + public static final String READER = "READER"; + public static final String WRITER = "WRITER"; } diff --git a/core/src/main/java/org/openmbee/mms/core/config/Constants.java b/core/src/main/java/org/openmbee/mms/core/config/Constants.java index c70762353..36c28b74b 100644 --- a/core/src/main/java/org/openmbee/mms/core/config/Constants.java +++ b/core/src/main/java/org/openmbee/mms/core/config/Constants.java @@ -9,6 +9,7 @@ public class Constants { public static final String PROJECT_KEY = "projects"; public static final String BRANCH_KEY = "refs"; public static final String ELEMENT_KEY = "elements"; + public static final String GROUP_KEY = "groups"; public static final String COMMIT_KEY = "commits"; public static final String WEBHOOK_KEY = "webhooks"; public static final String BRANCH_TYPE = "Branch"; @@ -40,9 +41,10 @@ public class Constants { public static final String ELEMENT_DELETE = "Element Already Deleted"; static { - aPriv = Arrays.asList("ORG_READ", "ORG_EDIT", "ORG_UPDATE_PERMISSIONS", "ORG_READ_PERMISSIONS", "ORG_CREATE_PROJECT", "ORG_DELETE", "PROJECT_READ", "PROJECT_EDIT", "PROJECT_READ_COMMITS", "PROJECT_CREATE_BRANCH", "PROJECT_DELETE", "PROJECT_UPDATE_PERMISSIONS", "PROJECT_READ_PERMISSIONS", "PROJECT_CREATE_WEBHOOKS", "BRANCH_READ", "BRANCH_EDIT_CONTENT", "BRANCH_DELETE", "BRANCH_UPDATE_PERMISSIONS", "BRANCH_READ_PERMISSIONS"); - rPriv = Arrays.asList("ORG_READ", "ORG_READ_PERMISSIONS", "PROJECT_READ", "PROJECT_READ_COMMITS", "PROJECT_READ_PERMISSIONS", "BRANCH_READ", "BRANCH_READ_PERMISSIONS"); - wPriv = Arrays.asList("ORG_READ", "ORG_EDIT", "ORG_READ_PERMISSIONS", "ORG_CREATE_PROJECT", "PROJECT_READ", "PROJECT_EDIT", "PROJECT_READ_COMMITS", "PROJECT_CREATE_BRANCH", "PROJECT_READ_PERMISSIONS", "PROJECT_CREATE_WEBHOOKS", "BRANCH_READ", "BRANCH_EDIT_CONTENT", "BRANCH_READ_PERMISSIONS"); + + aPriv = Arrays.asList("ORG_READ", "ORG_EDIT", "ORG_UPDATE_PERMISSIONS", "ORG_READ_PERMISSIONS", "ORG_CREATE_PROJECT", "ORG_DELETE", "PROJECT_READ", "PROJECT_EDIT", "PROJECT_READ_COMMITS", "PROJECT_CREATE_BRANCH", "PROJECT_DELETE", "PROJECT_UPDATE_PERMISSIONS", "PROJECT_READ_PERMISSIONS", "PROJECT_CREATE_WEBHOOKS", "BRANCH_READ", "BRANCH_EDIT_CONTENT", "BRANCH_DELETE", "BRANCH_UPDATE_PERMISSIONS", "BRANCH_READ_PERMISSIONS", "GROUP_READ", "GROUP_EDIT", "GROUP_DELETE", "GROUP_UPDATE_PERMISSIONS", "GROUP_READ_PERMISSIONS"); + rPriv = Arrays.asList("ORG_READ", "ORG_READ_PERMISSIONS", "PROJECT_READ", "PROJECT_READ_COMMITS", "PROJECT_READ_PERMISSIONS", "BRANCH_READ", "BRANCH_READ_PERMISSIONS", "GROUP_READ", "GROUP_READ_PERMISSIONS"); + wPriv = Arrays.asList("ORG_READ", "ORG_EDIT", "ORG_READ_PERMISSIONS", "ORG_CREATE_PROJECT", "PROJECT_READ", "PROJECT_EDIT", "PROJECT_READ_COMMITS", "PROJECT_CREATE_BRANCH", "PROJECT_READ_PERMISSIONS", "PROJECT_CREATE_WEBHOOKS", "BRANCH_READ", "BRANCH_EDIT_CONTENT", "BRANCH_READ_PERMISSIONS", "GROUP_READ", "GROUP_EDIT", "GROUP_READ_PERMISSIONS"); RPmap.put(ADMIN, aPriv); RPmap.put(READER, rPriv); RPmap.put(WRITER, wPriv); diff --git a/core/src/main/java/org/openmbee/mms/core/config/Privileges.java b/core/src/main/java/org/openmbee/mms/core/config/Privileges.java index 9356522b0..09b040d3b 100644 --- a/core/src/main/java/org/openmbee/mms/core/config/Privileges.java +++ b/core/src/main/java/org/openmbee/mms/core/config/Privileges.java @@ -20,6 +20,11 @@ public enum Privileges { BRANCH_EDIT_CONTENT, BRANCH_DELETE, BRANCH_UPDATE_PERMISSIONS, - BRANCH_READ_PERMISSIONS + BRANCH_READ_PERMISSIONS, + GROUP_READ, + GROUP_EDIT, + GROUP_DELETE, + GROUP_UPDATE_PERMISSIONS, + GROUP_READ_PERMISSIONS } diff --git a/core/src/main/java/org/openmbee/mms/core/dao/GroupPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/GroupPersistence.java index b7622f251..83a6e8bc9 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/GroupPersistence.java +++ b/core/src/main/java/org/openmbee/mms/core/dao/GroupPersistence.java @@ -11,4 +11,5 @@ public interface GroupPersistence { void delete(GroupJson groupJson); Optional findByName(String name); Collection findAll(); + boolean hasPublicPermissions(String projectId); } diff --git a/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegateFactory.java b/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegateFactory.java index 99b53daea..a353f8bf8 100644 --- a/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegateFactory.java +++ b/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegateFactory.java @@ -3,6 +3,7 @@ import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.json.RefJson; +import org.openmbee.mms.json.GroupJson; public interface PermissionsDelegateFactory { @@ -11,4 +12,6 @@ public interface PermissionsDelegateFactory { PermissionsDelegate getPermissionsDelegate(OrgJson organization); PermissionsDelegate getPermissionsDelegate(RefJson branch); + + PermissionsDelegate getPermissionsDelegate(GroupJson group); } diff --git a/core/src/main/java/org/openmbee/mms/core/objects/PermissionUpdateResponse.java b/core/src/main/java/org/openmbee/mms/core/objects/PermissionUpdateResponse.java index f8d1a661f..a822d34c0 100644 --- a/core/src/main/java/org/openmbee/mms/core/objects/PermissionUpdateResponse.java +++ b/core/src/main/java/org/openmbee/mms/core/objects/PermissionUpdateResponse.java @@ -29,10 +29,12 @@ public static class PermissionUpdate { private boolean inherited; + private String groupName; + public PermissionUpdate() {} public PermissionUpdate(Action action, String name, String role, String orgId, String orgName, - String projectId, String projectName, String branchId, boolean inherited) { + String projectId, String projectName, String branchId, String groupName, boolean inherited) { this.action = action; this.name = name; this.role = role; @@ -42,6 +44,7 @@ public PermissionUpdate(Action action, String name, String role, String orgId, S this.projectName = projectName; this.branchId = branchId; this.inherited = inherited; + this.groupName = groupName; } public Action getAction() { @@ -118,6 +121,14 @@ public boolean isInherited() { public void setInherited(boolean inherited) { this.inherited = inherited; } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } } public List getPermissionUpdates() { diff --git a/core/src/main/java/org/openmbee/mms/core/security/MethodSecurityService.java b/core/src/main/java/org/openmbee/mms/core/security/MethodSecurityService.java index bbab05f2a..aa0f94792 100644 --- a/core/src/main/java/org/openmbee/mms/core/security/MethodSecurityService.java +++ b/core/src/main/java/org/openmbee/mms/core/security/MethodSecurityService.java @@ -5,6 +5,7 @@ import java.util.concurrent.CompletionException; import org.openmbee.mms.core.dao.BranchPersistence; +import org.openmbee.mms.core.dao.GroupPersistence; import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.config.ContextHolder; @@ -14,6 +15,7 @@ import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.json.RefJson; +import org.openmbee.mms.json.GroupJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; @@ -26,6 +28,7 @@ public class MethodSecurityService { private ProjectPersistence projectPersistence; private BranchPersistence branchPersistence; private OrgPersistence orgPersistence; + private GroupPersistence groupPersistence; @Autowired public void setPermissionService(PermissionService permissionService) { @@ -47,6 +50,11 @@ public void setBranchPersistence(BranchPersistence branchPersistence) { this.branchPersistence = branchPersistence; } + @Autowired + public void setGroupPersistence(GroupPersistence groupPersistence) { + this.groupPersistence = groupPersistence; + } + public boolean hasOrgPrivilege(Authentication authentication, String orgId, String privilege, boolean allowAnonIfPublic) { CompletableFuture permissionsFuture = CompletableFuture.supplyAsync(() -> { @@ -104,6 +112,25 @@ public boolean hasBranchPrivilege(Authentication authentication, String projectI return completeFutures(permissionsFuture, existsFuture, "Branch"); } + public boolean hasGroupPrivilege(Authentication authentication, String groupName, String privilege, boolean allowAnonIfPublic) { + CompletableFuture permissionsFuture = CompletableFuture.supplyAsync(() -> + { + if (allowAnonIfPublic && permissionService.isGroupPublic(groupName)) { + return true; + } + if (authentication == null || authentication instanceof AnonymousAuthenticationToken) { + return false; + } + if (permissionService.hasGroupPrivilege(privilege, authentication.getName(), AuthenticationUtils.getGroups(authentication), groupName)) { + return true; + } + return false; + }); + + CompletableFuture existsFuture = CompletableFuture.supplyAsync(() -> groupExists(groupName)); + return completeFutures(permissionsFuture, existsFuture, "Group"); + } + private boolean orgExists(String orgId) { Optional o = orgPersistence.findById(orgId); return o.isPresent(); @@ -123,6 +150,11 @@ private boolean branchExists(String projectId, String branchId){ return branchesOption.isPresent(); } + private boolean groupExists(String groupName) { + Optional g = groupPersistence.findByName(groupName); + return g.isPresent(); + } + private boolean completeFutures(CompletableFuture permissionsFuture, CompletableFuture existsFuture, String context) { try { if (!isBooleanFutureTrue(permissionsFuture)){ diff --git a/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java b/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java index c53ad3dab..c213f7a03 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java +++ b/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java @@ -8,6 +8,7 @@ import org.openmbee.mms.core.dao.BranchPersistence; import org.openmbee.mms.core.dao.OrgPersistence; import org.openmbee.mms.core.dao.ProjectPersistence; +import org.openmbee.mms.core.dao.GroupPersistence; import org.openmbee.mms.core.delegation.PermissionsDelegate; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.exceptions.NotFoundException; @@ -16,6 +17,7 @@ import org.openmbee.mms.core.objects.PermissionUpdateResponse; import org.openmbee.mms.core.objects.PermissionUpdatesResponse; import org.openmbee.mms.core.utils.PermissionsDelegateUtil; +import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.json.RefJson; @@ -33,6 +35,8 @@ public class DefaultPermissionService implements PermissionService { private BranchPersistence branchPersistence; private ProjectPersistence projectPersistence; private OrgPersistence orgPersistence; + private GroupPersistence groupPersistence; + private PermissionsDelegateUtil permissionsDelegateUtil; @Autowired @@ -55,6 +59,11 @@ public void setOrgPersistence(OrgPersistence orgPersistence) { this.orgPersistence = orgPersistence; } + @Autowired + public void setGroupPersistence(GroupPersistence groupPersistence) { + this.groupPersistence = groupPersistence; + } + @Override public void initOrgPerms(String orgId, String creator) { OrgJson organization = getOrganization(orgId); @@ -85,6 +94,14 @@ public void initBranchPerms(String projectId, String branchId, boolean inherit, recalculateInheritedPerms(branch); } + @Override + @Transactional + public void initGroupPerms(String groupName, String creator) { + GroupJson group = getGroup(groupName); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(group); + permissionsDelegate.initializePermissions(creator); + } + @Override public PermissionUpdatesResponse updateOrgUserPerms(PermissionUpdateRequest req, String orgId) { OrgJson organization = getOrganization(orgId); @@ -98,7 +115,7 @@ public PermissionUpdatesResponse updateOrgUserPerms(PermissionUpdateRequest req, responseBuilder.insert(recalculateInheritedPerms(project)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -113,7 +130,7 @@ public PermissionUpdatesResponse updateOrgGroupPerms(PermissionUpdateRequest req responseBuilder.insert(recalculateInheritedPerms(project)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -128,7 +145,7 @@ public PermissionUpdatesResponse updateProjectUserPerms(PermissionUpdateRequest responseBuilder.insert(recalculateInheritedPerms(b)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -143,7 +160,7 @@ public PermissionUpdatesResponse updateProjectGroupPerms(PermissionUpdateRequest responseBuilder.insert(recalculateInheritedPerms(b)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -163,6 +180,26 @@ public PermissionUpdateResponse updateBranchGroupPerms(PermissionUpdateRequest r return permissionsDelegate.updateGroupPermissions(req); } + @Override + public PermissionUpdatesResponse updateGroupUserPerms(PermissionUpdateRequest req, String groupName) { + GroupJson group = getGroup(groupName); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(group); + PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); + responseBuilder.getUsers().insert(permissionsDelegate.updateUserPermissions(req)); + + return responseBuilder.getPermissionUpdatesResponse(); + } + + @Override + public PermissionUpdatesResponse updateGroupGroupPerms(PermissionUpdateRequest req, String groupName) { + GroupJson group = getGroup(groupName); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(group); + PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); + responseBuilder.getGroups().insert(permissionsDelegate.updateGroupPermissions(req)); + + return responseBuilder.getPermissionUpdatesResponse(); + } + @Override public PermissionUpdatesResponse setProjectInherit(boolean isInherit, String projectId) { PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); @@ -172,7 +209,7 @@ public PermissionUpdatesResponse setProjectInherit(boolean isInherit, String pro if (permissionsDelegate.setInherit(isInherit)) { responseBuilder.insert(recalculateInheritedPerms(project)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -184,7 +221,7 @@ public PermissionUpdatesResponse setBranchInherit(boolean isInherit, String proj if (permissionsDelegate.setInherit(isInherit)) { responseBuilder.insert(recalculateInheritedPerms(branch)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -203,6 +240,15 @@ public boolean setProjectPublic(boolean isPublic, String projectId) { return true; } + @Override + @Transactional + public boolean setGroupPublic(boolean isPublic, String groupName) { + GroupJson group = getGroup(groupName); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(group); + permissionsDelegate.setPublic(isPublic); + return true; + } + @Override public boolean hasOrgPrivilege(String privilege, String user, Set groups, String orgId) { if (groups.contains(AuthorizationConstants.MMSADMIN)) return true; @@ -230,6 +276,17 @@ public boolean hasBranchPrivilege(String privilege, String user, Set gro return permissionsDelegate.hasPermission(user, groups, privilege); } + @Override + public boolean hasGroupPrivilege(String privilege, String user, Set groups, String groupName) { + //Return true, however admin are only allowed "delete" perms on Remote group types + if (groups.contains(AuthorizationConstants.MMSADMIN)) return true; + + GroupJson group = getGroup(groupName); + + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(group); + return permissionsDelegate.hasPermission(user, groups, privilege); + } + @Override public boolean isProjectInherit(String projectId) { return projectPersistence.inheritsPermissions(projectId); @@ -250,6 +307,11 @@ public boolean isProjectPublic(String projectId) { return projectPersistence.hasPublicPermissions(projectId); } + @Override + public boolean isGroupPublic(String groupName) { + return groupPersistence.hasPublicPermissions(groupName); + } + @Override public PermissionResponse getOrgGroupRoles(String orgId) { OrgJson organization = getOrganization(orgId); @@ -302,6 +364,23 @@ public PermissionResponse getBranchUserRoles(String projectId, String branchId) return permissionsDelegate.getUserRoles(); } + @Override + @Transactional + public PermissionResponse getGroupGroupRoles(String groupName) { + GroupJson group = getGroup(groupName); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(group); + return permissionsDelegate.getGroupRoles(); + } + + @Override + @Transactional + public PermissionResponse getGroupUserRoles(String groupName) { + GroupJson group = getGroup(groupName); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(group); + return permissionsDelegate.getUserRoles(); + } + + private PermissionUpdatesResponse recalculateInheritedPerms(ProjectJson project) { PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); @@ -312,8 +391,7 @@ private PermissionUpdatesResponse recalculateInheritedPerms(ProjectJson project) for (RefJson branch : branches) { responseBuilder.insert(recalculateInheritedPerms(branch)); } - - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } private PermissionUpdatesResponse recalculateInheritedPerms(RefJson branch) { @@ -340,6 +418,15 @@ private ProjectJson getProject(String projectId) { return proj.get(); } + private GroupJson getGroup(String groupName) { + Optional group = groupPersistence.findByName(groupName); + + if (!group.isPresent()) { + throw new NotFoundException("Group " + groupName + " not found"); + } + return group.get(); + } + private enum BRANCH_NOTFOUND_BEHAVIOR {THROW, CREATE, IGNORE} private RefJson getBranch(String projectId, String branchId, BRANCH_NOTFOUND_BEHAVIOR mode) { diff --git a/core/src/main/java/org/openmbee/mms/core/services/PermissionService.java b/core/src/main/java/org/openmbee/mms/core/services/PermissionService.java index 19e048439..847fc00ec 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/PermissionService.java +++ b/core/src/main/java/org/openmbee/mms/core/services/PermissionService.java @@ -14,6 +14,8 @@ public interface PermissionService { void initBranchPerms(String projectId, String branchId, boolean inherit, String creator); + void initGroupPerms(String groupName, String creator); + PermissionUpdatesResponse updateOrgUserPerms(PermissionUpdateRequest req, String orgId); PermissionUpdatesResponse updateOrgGroupPerms(PermissionUpdateRequest req, String orgId); @@ -26,6 +28,10 @@ public interface PermissionService { PermissionUpdateResponse updateBranchGroupPerms(PermissionUpdateRequest req, String projectId, String branchId); + PermissionUpdatesResponse updateGroupUserPerms(PermissionUpdateRequest req, String groupName); + + PermissionUpdatesResponse updateGroupGroupPerms(PermissionUpdateRequest req, String groupName); + PermissionUpdatesResponse setProjectInherit(boolean isInherit, String projectId); PermissionUpdatesResponse setBranchInherit(boolean isInherit, String projectId, String branchId); @@ -34,12 +40,16 @@ public interface PermissionService { boolean setProjectPublic(boolean isPublic, String projectId); + boolean setGroupPublic(boolean isPublic, String groupName); + boolean hasOrgPrivilege(String privilege, String user, Set groups, String orgId); boolean hasProjectPrivilege(String privilege, String user, Set groups, String projectId); boolean hasBranchPrivilege(String privilege, String user, Set groups, String projectId, String branchId); + boolean hasGroupPrivilege(String privilege, String user, Set groups, String groupName); + boolean isProjectInherit(String projectId); boolean isBranchInherit(String projectId, String branchId); @@ -48,6 +58,8 @@ public interface PermissionService { boolean isProjectPublic(String projectId); + boolean isGroupPublic(String groupName); + PermissionResponse getOrgGroupRoles(String orgId); PermissionResponse getOrgUserRoles(String orgId); @@ -59,4 +71,8 @@ public interface PermissionService { PermissionResponse getBranchGroupRoles(String projectId, String branchId); PermissionResponse getBranchUserRoles(String projectId, String branchId); + + PermissionResponse getGroupGroupRoles(String groupName); + + PermissionResponse getGroupUserRoles(String groupName); } diff --git a/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java b/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java index c0378b3e8..9c98f4415 100644 --- a/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java +++ b/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java @@ -4,6 +4,7 @@ import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.services.DefaultPermissionService; +import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.json.RefJson; @@ -67,4 +68,17 @@ public PermissionsDelegate getPermissionsDelegate(final RefJson branch) { " of project " + (branch.getProjectId() == null ? "?" : branch.getProjectId())); } + + public PermissionsDelegate getPermissionsDelegate(final GroupJson group) { + Optional permissionsDelegate = permissionsDelegateFactories.stream() + .map(v -> v.getPermissionsDelegate(group)).filter(Objects::nonNull).findFirst(); + + if(permissionsDelegate.isPresent()) { + return permissionsDelegate.get(); + } + + throw new InternalErrorException( + "No valid permissions scheme found for project " + group.getName() + + " (" + group.getName() + ")"); + } } diff --git a/data/src/main/java/org/openmbee/mms/data/dao/ProjectDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/ProjectDAO.java index e3f791727..b4124da15 100644 --- a/data/src/main/java/org/openmbee/mms/data/dao/ProjectDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/ProjectDAO.java @@ -4,8 +4,6 @@ import java.util.Optional; import org.openmbee.mms.data.domains.global.Project; -import javax.transaction.Transactional; - public interface ProjectDAO { Optional findByProjectId(String id); diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java b/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java index 9766154af..a489079dd 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java @@ -1,13 +1,13 @@ package org.openmbee.mms.data.domains.global; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.Collection; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.ManyToMany; -import javax.persistence.Table; +import javax.persistence.*; @Entity -@Table(name = "groups") +@Table(name = "groups", uniqueConstraints = @UniqueConstraint(columnNames = "name")) public class Group extends Base { public static final String NAME_COLUMN = "name"; @@ -18,9 +18,28 @@ public class Group extends Base { @ManyToMany(mappedBy = "groups") private Collection users; + @JsonIgnore + private VALID_GROUP_TYPES type; + + @JsonProperty("type") + private String typeString; + + @JsonIgnore + @OneToMany(mappedBy = "group", cascade = CascadeType.REMOVE, orphanRemoval = true) + private Collection groupPerms; + + @JsonIgnore + @OneToMany(mappedBy = "group", cascade = CascadeType.REMOVE, orphanRemoval = true) + private Collection userPerms; + + @JsonProperty("public") + private boolean isPublic; + public Group() {} public Group(String name) { this.name = name; + this.type = VALID_GROUP_TYPES.REMOTE; + this.typeString = VALID_GROUP_TYPES.REMOTE.toString().toLowerCase(); } public String getName() { @@ -31,6 +50,30 @@ public void setName(String name) { this.name = name; } + public enum VALID_GROUP_TYPES {LOCAL, REMOTE} + + public VALID_GROUP_TYPES getType() { + return this.type; + } + + public String getTypeString() { + return this.typeString; + } + + public void setType(VALID_GROUP_TYPES t) { + this.type = t; + this.typeString = t.toString().toLowerCase(); + } + + public void setType(String t) { + if (t.equalsIgnoreCase("local")) { + this.type = VALID_GROUP_TYPES.LOCAL; + }else { + this.type = VALID_GROUP_TYPES.REMOTE; + } + this.typeString = t; + } + public Collection getUsers() { return users; } @@ -38,4 +81,28 @@ public Collection getUsers() { public void setUsers(Collection users) { this.users = users; } + + public Collection getGroupPerms() { + return this.groupPerms; + } + + public void setGroupPerms(Collection groupPerms) { + this.groupPerms = groupPerms; + } + + public Collection getUserPerms() { + return this.userPerms; + } + + public void setUserPerms(Collection userPerms) { + this.userPerms = userPerms; + } + + public boolean isPublic() { + return isPublic; + } + + public void setPublic(boolean aPublic) { + isPublic = aPublic; + } } diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/GroupGroupPerm.java b/data/src/main/java/org/openmbee/mms/data/domains/global/GroupGroupPerm.java new file mode 100644 index 000000000..e1870359f --- /dev/null +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/GroupGroupPerm.java @@ -0,0 +1,56 @@ +package org.openmbee.mms.data.domains.global; + +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "group_group_perms", + indexes = { + @Index(columnList = "group_id"), + @Index(columnList = "group_id,groupPerm_id") + }) +public class GroupGroupPerm extends Base { + + @ManyToOne + private Group group; + + @ManyToOne + private Group groupPerm; + + @ManyToOne + private Role role; + + public GroupGroupPerm() {} + + public GroupGroupPerm(Group group, Group u, Role r) { + this.group = group; + this.groupPerm = u; + this.role = r; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public Group getGroupPerm() { + return groupPerm; + } + + public void setGroupPerm(Group group) { + this.groupPerm = group; + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } +} diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/GroupUserPerm.java b/data/src/main/java/org/openmbee/mms/data/domains/global/GroupUserPerm.java new file mode 100644 index 000000000..c7bfca1de --- /dev/null +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/GroupUserPerm.java @@ -0,0 +1,56 @@ +package org.openmbee.mms.data.domains.global; + +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "group_user_perms", + indexes = { + @Index(columnList = "group_id"), + @Index(columnList = "group_id,user_id") + }) +public class GroupUserPerm extends Base { + + @ManyToOne + private Group group; + + @ManyToOne + private User user; + + @ManyToOne + private Role role; + + public GroupUserPerm() {} + + public GroupUserPerm(Group p, User u, Role r) { + this.group = p; + this.user = u; + this.role = r; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } +} diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/User.java b/data/src/main/java/org/openmbee/mms/data/domains/global/User.java index e25bc5992..bc2ccdb27 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/User.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/User.java @@ -3,6 +3,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import javax.persistence.*; @JsonInclude(JsonInclude.Include.NON_EMPTY) @@ -11,7 +13,9 @@ @UniqueConstraint(columnNames = "email")}) public class User extends Base { + @Column(unique = true) private String username; + private String email; private String firstName; private String lastName; @@ -35,18 +39,21 @@ public class User extends Base { @JsonIgnore @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "users_groups", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "group_id", referencedColumnName = "id")) - private Collection groups; + @JoinTable(name = "users_groups", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "group_id", referencedColumnName = "id"), uniqueConstraints=@UniqueConstraint(columnNames={"user_id","group_id"})) + private Set groups; public User() { + this.groups = new HashSet<>(); } public User(String email, String username, String password, String firstName, String lastName, boolean admin) { this.email = email; + this.username = username; this.password = password; this.firstName = firstName; this.lastName = lastName; this.admin = admin; + this.groups = new HashSet<>(); } public String getUsername() { @@ -56,7 +63,7 @@ public String getUsername() { public void setUsername(String username) { this.username = username; } - + public String getEmail() { return email; } @@ -133,7 +140,7 @@ public Collection getGroups() { return groups; } - public void setGroups(Collection groups) { + public void setGroups(Set groups) { this.groups = groups; } @@ -144,4 +151,5 @@ public boolean isAdmin() { public void setAdmin(boolean admin) { this.admin = admin; } + } diff --git a/docker-compose.yml b/docker-compose.yml index 2c15227ee..406fea542 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,10 +32,10 @@ services: container_name: mms hostname: mms environment: - - "SPRING_PROFILES_ACTIVE=test" + - "SPRING_PROFILES_ACTIVE=local" depends_on: - postgres - elasticsearch - minio - ports: + ports: - 8080:8080 \ No newline at end of file diff --git a/docs/modules/localuser.rst b/docs/modules/localuser.rst index 352f2ce41..810074853 100644 --- a/docs/modules/localuser.rst +++ b/docs/modules/localuser.rst @@ -1 +1 @@ -.. include:: ../../localuser/README.rst \ No newline at end of file +.. include:: ../../localauth/README.rst \ No newline at end of file diff --git a/example/artifacts.postman_collection.json b/example/artifacts.postman_collection.json index 3e7027937..262cc298c 100644 --- a/example/artifacts.postman_collection.json +++ b/example/artifacts.postman_collection.json @@ -751,12 +751,12 @@ "raw": "{\n\t\"username\": \"other\",\n\t\"password\": \"other\"\n}" }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, diff --git a/example/example.gradle b/example/example.gradle index e25098d06..e889db43f 100644 --- a/example/example.gradle +++ b/example/example.gradle @@ -16,7 +16,8 @@ apply plugin: 'io.spring.dependency-management' dependencies { implementation( project(':authenticator'), - project(':localuser'), + project(':localauth'), + project(':users'), project(':ldap'), project(':cameo'), project(':elastic'), diff --git a/example/groups.postman_collection.json b/example/groups.postman_collection.json index 58c913300..1da4eef44 100644 --- a/example/groups.postman_collection.json +++ b/example/groups.postman_collection.json @@ -67,28 +67,45 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});" - ], + "pm.test(\"response has 1 group\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.groups.length).to.eql(1);", + " pm.expect(jsonData.groups[0]['name']).to.include('localgroup')", + "});" + ], "type": "text/javascript" } } ], "request": { - "method": "PUT", - "header": [], + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], "url": { - "raw": "{{host}}/groups/localgroup", + "raw": "{{host}}/groups", "host": [ "{{host}}" ], "path": [ - "groups", - "localgroup" + "groups" ] - } - }, + }, + "body": { + "mode": "raw", + "raw": "{\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\" : \"localgroup\",\n\t\t\t\"type\" : \"local\"\n\t\t}\n\t]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "response": [] }, { @@ -125,12 +142,12 @@ } }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, @@ -189,10 +206,14 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"there's at least 1 group\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.groups).to.include('localgroup');", - "});" + "pm.test(\"More than 1 group exists\", () => {", + " const response = pm.response.json();", + " const expectedObject = {", + " name: \"test\"", + "};", + "pm.expect(response.groups.length).to.be.at.least(1);", + "pm.expect(_.some(response.groups, expectedObject)).to.be.true;", + "});" ], "type": "text/javascript" } @@ -233,13 +254,14 @@ "method": "GET", "header": [], "url": { - "raw": "{{host}}/groups/localgroup", + "raw": "{{host}}/groups/localgroup/users", "host": [ "{{host}}" ], "path": [ "groups", - "localgroup" + "localgroup", + "users" ] } }, @@ -261,18 +283,26 @@ } ], "request": { - "method": "PUT", + "method": "POST", "header": [], "url": { - "raw": "{{host}}/groups/localgroup", + "raw": "{{host}}/groups", "host": [ "{{host}}" ], "path": [ - "groups", - "localgroup" + "groups" ] - } + }, + "body": { + "mode": "raw", + "raw": "{\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\" : \"localgroup\",\n\t\t\t\"type\" : \"local\"\n\t\t}\n\t]\n}", + "options": { + "raw": { + "language": "json" + } + } + } }, "response": [] }, @@ -560,6 +590,49 @@ }, "response": [] }, + { + "name": "change type of not empty group", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "pm.test(\"group rejected\", function() {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.rejected.length).to.eql(1);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{host}}/groups", + "host": [ + "{{host}}" + ], + "path": [ + "groups" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\" : \"localgroup\",\n\t\t\t\"type\" : \"postman\"\n\t\t}\n\t]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "response": [] + }, { "name": "remove user from group", "event": [ @@ -606,6 +679,46 @@ }, "response": [] }, + { + "name": "modify existing group", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + " pm.expect(pm.response.json().groups[0].type).to.equal(\"postman\")", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{host}}/groups", + "host": [ + "{{host}}" + ], + "path": [ + "groups" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\" : \"localgroup\",\n\t\t\t\"type\" : \"postman\"\n\t\t}\n\t]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "response": [] + }, { "name": "delete empty group", "event": [ diff --git a/example/localauth.postman_collection.json b/example/localauth.postman_collection.json index 278104660..25dcff515 100644 --- a/example/localauth.postman_collection.json +++ b/example/localauth.postman_collection.json @@ -92,12 +92,12 @@ "raw": "{\n\t\"username\" : \"user1\",\n\t\"password\" : \"password1\"\n}" }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, @@ -179,12 +179,12 @@ "raw": "{\n\t\"username\" : \"user2\",\n\t\"password\" : \"password2\"\n}" }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, @@ -611,12 +611,12 @@ "raw": "{\n\t\"username\" : \"admin2\",\n\t\"password\" : \"adminpassword\",\n\t\"admin\": true\n}" }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, diff --git a/example/permissions.postman_collection.json b/example/permissions.postman_collection.json index 106f974e5..b7b042485 100644 --- a/example/permissions.postman_collection.json +++ b/example/permissions.postman_collection.json @@ -183,6 +183,102 @@ }, "response": [] }, + { + "name": "create remote group", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has 1 group\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.groups.length).to.eql(1);", + " pm.expect(jsonData.groups[0]['name']).to.include('remotegroup')", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/groups", + "host": [ + "{{host}}" + ], + "path": [ + "groups" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n\t\"groups\": [\n\t]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + + "response": [] + }, + { + "name": "create groups", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has 1 group\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.groups.length).to.eql(1);", + " pm.expect(jsonData.groups[0]['name']).to.include('localgroup')", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/groups", + "host": [ + "{{host}}" + ], + "path": [ + "groups" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\" : \"localgroup\",\n\t\t\t\"type\" : \"local\"\n\t\t},\n\t\t{\n\t\t\t\"name\" : \"remotegroup\",\n\t\t\t\"type\" : \"postman\"\n\t\t,\n\t\t\t\"public\" : false\n\t\t}\n\t]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + + "response": [] + }, { "name": "check anonymous is rejected for project permissions-aa", "event": [ @@ -218,6 +314,41 @@ }, "response": [] }, + { + "name": "check anonymous is rejected for group remotegroup", + "event": [ + { + "listen": "test", + "script": { + "id": "1b237e80-4487-4562-ba7b-274f0ef94a21", + "exec": [ + "pm.test(\"Status code is 401\", function () {", + " pm.response.to.have.status(401);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host}}/groups/remotegroup", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "permissions-aa" + ] + } + }, + "response": [] + }, { "name": "create reader user", "event": [ @@ -248,12 +379,12 @@ "raw": "{\n\t\"username\": \"reader\",\n\t\"password\": \"reader\"\n}" }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, @@ -302,12 +433,12 @@ "raw": "{\n\t\"username\": \"dummy\",\n\t\"password\": \"dummy\"\n}" }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, @@ -343,12 +474,12 @@ "raw": "{\n\t\"username\": \"writer\",\n\t\"password\": \"writer\"\n}" }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, diff --git a/example/src/main/resources/application-test.properties b/example/src/main/resources/application-test.properties index 4ad63d33b..e080896ac 100644 --- a/example/src/main/resources/application-test.properties +++ b/example/src/main/resources/application-test.properties @@ -12,6 +12,8 @@ jwt.header=Authorization rdb.project.prefix=mms +# Local Authentication configuration + # See ldap module for example configuration ldap.enabled=false ldap.provider.base=ou=something,dc=openmbee,dc=org diff --git a/example/twc.postman_collection.json b/example/twc.postman_collection.json index 84e0aec1f..18f750b49 100644 --- a/example/twc.postman_collection.json +++ b/example/twc.postman_collection.json @@ -121,12 +121,12 @@ "raw": "{\n\t\"username\": \"twcuser\",\n\t\"password\": \"twcuser\"\n}" }, "url": { - "raw": "{{host}}/user", + "raw": "{{host}}/users", "host": [ "{{host}}" ], "path": [ - "user" + "users" ] } }, diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/PermissionInit.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/PermissionInit.java index caa3cd062..1159e8a17 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/PermissionInit.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/PermissionInit.java @@ -1,17 +1,18 @@ package org.openmbee.mms.federatedpersistence.config; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.data.domains.global.Privilege; -import org.openmbee.mms.data.domains.global.Role; -import org.openmbee.mms.rdb.repositories.GroupRepository; -import org.openmbee.mms.rdb.repositories.PrivilegeRepository; -import org.openmbee.mms.rdb.repositories.RoleRepository; +import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.services.PermissionService; +import org.openmbee.mms.data.domains.global.*; +import org.openmbee.mms.rdb.repositories.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import javax.transaction.Transactional; import java.util.*; + import static org.openmbee.mms.core.config.Constants.RPmap; import static org.openmbee.mms.core.config.Constants.aPriv; @@ -19,12 +20,18 @@ @Transactional public class PermissionInit implements ApplicationListener { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + private PrivilegeRepository privRepo; private RoleRepository roleRepo; private GroupRepository groupRepo; + private UserRepository userRepo; + + private GroupGroupPermRepository groupGroupPermRepo; + @Autowired public void setPrivRepo(PrivilegeRepository privRepo) { this.privRepo = privRepo; @@ -40,9 +47,18 @@ public void setGroupRepo(GroupRepository groupRepo) { this.groupRepo = groupRepo; } + @Autowired + public void setUserRepo(UserRepository userRepo) { + this.userRepo = userRepo; + } + + @Autowired + public void setGroupGroupPermRepo(GroupGroupPermRepository groupGroupPermRepo) { + this.groupGroupPermRepo = groupGroupPermRepo; + } + @Override public void onApplicationEvent(final ApplicationReadyEvent event) { - for (String role : RPmap.keySet()) { Optional roleIn = roleRepo.findByName(role); if (!(roleIn.isPresent())) { @@ -74,12 +90,33 @@ public void onApplicationEvent(final ApplicationReadyEvent event) { role.setPrivileges(pSet); roleRepo.saveAndFlush(role); } - - Optional evGroupIn = groupRepo.findByName("everyone"); - if (!(evGroupIn.isPresent())) { - Group evGroup = new Group(); - evGroup.setName("everyone"); - groupRepo.saveAndFlush(evGroup); + //Ensure ev group exists and is correct in the event someone messes it up + Optional evGroupIn = groupRepo.findByName(AuthorizationConstants.EVERYONE); + Group evGroup; + if (evGroupIn.isEmpty()) { + evGroup = new Group(); + evGroup.setName(AuthorizationConstants.EVERYONE); + }else { + evGroup = evGroupIn.get(); + } + if (evGroup.getType() != Group.VALID_GROUP_TYPES.REMOTE) { + evGroup.setType("everyone"); } + evGroup.setPublic(true); + groupRepo.saveAndFlush(evGroup); + Optional evGroupPermOp = groupGroupPermRepo.findByGroupAndGroupPerm(evGroup,evGroup); + Optional evRole = roleRepo.findByName(AuthorizationConstants.READER); + if (evGroupPermOp.isEmpty() && evRole.isPresent()) { + evGroupPermOp = Optional.of(new GroupGroupPerm(evGroup, evGroup, evRole.get())); + groupGroupPermRepo.saveAndFlush(evGroupPermOp.get()); + } + //Ensure permissions are correct on ev group in the event someone messes it up + if (evGroupPermOp.isPresent() && evRole.isPresent() && evGroupPermOp.get().getRole() != evRole.get()) { + GroupGroupPerm evGroupPerm = evGroupPermOp.get(); + evGroupPerm.setRole(evRole.get()); + groupGroupPermRepo.saveAndFlush(evGroupPerm); + } + } + } \ No newline at end of file diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java index f85d3d94f..52f8c4a94 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java @@ -1,6 +1,7 @@ package org.openmbee.mms.federatedpersistence.dao; import org.openmbee.mms.core.dao.GroupPersistence; +import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.data.domains.global.Group; import org.openmbee.mms.federatedpersistence.utils.FederatedJsonUtils; import org.openmbee.mms.json.GroupJson; @@ -61,4 +62,13 @@ private GroupJson getJson(Group saved) { json.merge(jsonUtils.convertToMap(saved)); return json; } + + @Override + public boolean hasPublicPermissions(String groupName) { + Optional group = groupRepository.findByName(groupName); + if (group.isEmpty()) { + throw new NotFoundException("group " + groupName + " not found"); + } + return group.get().isPublic(); + } } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultBranchPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultBranchPermissionsDelegate.java index aaeb0bdb1..fa3968987 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultBranchPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultBranchPermissionsDelegate.java @@ -432,6 +432,6 @@ public PermissionUpdatesResponse recalculateInheritedPerms() { branchGroupPermRepo.saveAll(groupPermissions.getAll()); responseBuilder.getGroups().insertPermissionUpdates_BranchGroupPerm(PermissionUpdateResponse.Action.ADD, groupPermissions.getAll()); - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java new file mode 100644 index 000000000..3d58598c7 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java @@ -0,0 +1,290 @@ +package org.openmbee.mms.federatedpersistence.permissions; + +import org.openmbee.mms.core.builders.PermissionUpdateResponseBuilder; +import org.openmbee.mms.core.builders.PermissionUpdatesResponseBuilder; +import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.objects.PermissionResponse; +import org.openmbee.mms.core.objects.PermissionUpdateRequest; +import org.openmbee.mms.core.objects.PermissionUpdateResponse; +import org.openmbee.mms.core.objects.PermissionUpdatesResponse; +import org.openmbee.mms.data.domains.global.Group; +import org.openmbee.mms.data.domains.global.GroupGroupPerm; +import org.openmbee.mms.data.domains.global.GroupUserPerm; +import org.openmbee.mms.data.domains.global.Privilege; +import org.openmbee.mms.data.domains.global.Role; +import org.openmbee.mms.data.domains.global.User; +import org.openmbee.mms.federatedpersistence.permissions.exceptions.PermissionException; +import org.openmbee.mms.rdb.repositories.GroupGroupPermRepository; +import org.openmbee.mms.rdb.repositories.GroupUserPermRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.util.Pair; +import org.springframework.http.HttpStatus; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static org.openmbee.mms.core.config.AuthorizationConstants.EVERYONE; + +public class DefaultGroupPermissionsDelegate extends AbstractDefaultPermissionsDelegate { + + private GroupUserPermRepository groupUserPermRepo; + private GroupGroupPermRepository groupGroupPermRepo; + + private final Group group; + + public DefaultGroupPermissionsDelegate(Group group) { + this.group = group; + } + + @Autowired + public void setGroupUserPermRepo(GroupUserPermRepository groupUserPermRepo) { + this.groupUserPermRepo = groupUserPermRepo; + } + + @Autowired + public void setGroupGroupPermRepo(GroupGroupPermRepository groupGroupPermRepo) { + this.groupGroupPermRepo = groupGroupPermRepo; + } + + @Override + public boolean hasPermission(String user, Set groupPerms, String privilege) { + + Optional priv = getPrivRepo().findByName(privilege); + if (priv.isEmpty()) { + throw new PermissionException(HttpStatus.BAD_REQUEST, "No such privilege"); + } + + //Return false if group is remotely managed + if (group.getType().equals(Group.VALID_GROUP_TYPES.REMOTE) && privilege.equalsIgnoreCase("GROUP_EDIT")) { + throw new PermissionException(HttpStatus.BAD_REQUEST, "Unable to edit remote groups."); + } + + Set roles = priv.get().getRoles(); + if (groupUserPermRepo.existsByGroupAndUser_UsernameAndRoleIn(group, user, roles)) { + return true; + } + return !groupPerms.isEmpty() && groupGroupPermRepo.existsByGroupAndGroupPerm_NameInAndRoleIn(group, groupPerms, roles); + } + + @Override + public boolean hasGroupPermissions(String group, String privilege) { + for (GroupGroupPerm perm: groupGroupPermRepo.findAllByGroup_Name(group)) { + if (perm.getGroup().getName().equals(group) && perm.getRole().getPrivileges().stream() + .anyMatch(v -> v.getName().equals(privilege))) { + return true; + } + } + return false; + } + + @Override + public void initializePermissions(String creator) { + initializePermissions(creator, false); + } + + @Override + public void initializePermissions(String creator, boolean inherit) { + if(inherit) { + throw new IllegalArgumentException("Cannot inherit permissions for a Group"); + } + + Optional user = getUserRepo().findByUsername(creator); + Optional role = getRoleRepo().findByName(AuthorizationConstants.ADMIN); + + if (user.isEmpty()) { + throw new PermissionException(HttpStatus.NOT_FOUND, "User not found"); + } else if (role.isEmpty()) { + throw new PermissionException(HttpStatus.NOT_FOUND, "Role not found"); + } + + GroupUserPerm perm = new GroupUserPerm(group, user.get(), role.get()); + groupUserPermRepo.save(perm); + + Optional eRole = getRoleRepo().findByName(AuthorizationConstants.READER); + Optional ePerm = getGroupRepo().findByName(AuthorizationConstants.EVERYONE); + + if (ePerm.isEmpty()) { + return; + } else if (eRole.isEmpty()) { + throw new PermissionException(HttpStatus.NOT_FOUND, "Role not found"); + } + + GroupGroupPerm evPerm = new GroupGroupPerm(group, ePerm.get(), eRole.get()); + groupGroupPermRepo.save(evPerm); + } + + @Override + public boolean setInherit(boolean isInherit) { + if(isInherit) { + throw new IllegalArgumentException("Cannot inherit permissions for a Group"); + } + return false; + } + + @Override + public void setPublic(boolean isPublic) { + group.setPublic(isPublic); + getGroupRepo().save(group); + } + + @Override + public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest req) { + PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + + switch(req.getAction()) { + case MODIFY: + for (PermissionUpdateRequest.Permission p: req.getPermissions()) { + Optional user = getUserRepo().findByUsername(p.getName()); + Optional role = getRoleRepo().findByName(p.getRole()); + if (user.isEmpty() || role.isEmpty()) { + //throw exception or skip + continue; + } + Optional exist = groupUserPermRepo.findByGroupAndUser(group, user.get()); + GroupUserPerm p1; + if (exist.isPresent()) { + p1 = exist.get(); + if (!role.get().equals(p1.getRole())) { + responseBuilder.insertPermissionUpdate(PermissionUpdateResponse.Action.REMOVE, p1); + p1.setRole(role.get()); + groupUserPermRepo.save(p1); + } else { + continue; + } + } else { + p1 = new GroupUserPerm(group, user.get(), role.get()); + groupUserPermRepo.save(p1); + } + responseBuilder.insertPermissionUpdate(PermissionUpdateResponse.Action.ADD, p1); + } + break; + case REPLACE: + responseBuilder.insertPermissionUpdates_GroupUserPerm(PermissionUpdateResponse.Action.REMOVE, + groupUserPermRepo.findAllByGroup(group)); + groupUserPermRepo.deleteByGroup(group); + + for (PermissionUpdateRequest.Permission p: req.getPermissions()) { + Optional user = getUserRepo().findByUsername(p.getName()); + Optional role = getRoleRepo().findByName(p.getRole()); + if (!user.isPresent() || !role.isPresent()) { + //throw exception or skip + continue; + } + GroupUserPerm p1 = new GroupUserPerm(group, user.get(), role.get()); + groupUserPermRepo.save(p1); + responseBuilder.insertPermissionUpdate(PermissionUpdateResponse.Action.ADD, p1); + } + break; + case REMOVE: + Set users = new HashSet<>(); + req.getPermissions().forEach(p -> { + Optional user = getUserRepo().findByUsername(p.getName()); + if(! user.isPresent()) { + //throw or skip; + return; + } + users.add(p.getName()); + responseBuilder.insertPermissionUpdate(PermissionUpdateResponse.Action.REMOVE, + groupUserPermRepo.findByGroupAndUser(group, user.get()).orElse(null)); + }); + groupUserPermRepo.deleteByGroupAndUser_UsernameIn(group, users); + break; + } + return responseBuilder.getPermissionUpdateResponse(); + } + + @Override + public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest req) { + PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + switch(req.getAction()) { + case MODIFY: + for (PermissionUpdateRequest.Permission p: req.getPermissions()) { + Pair pair = getGroupAndRole(p); + if (pair.getFirst() == null || pair.getSecond() == null) { + continue; + } + Optional exist = groupGroupPermRepo.findByGroupAndGroupPerm(group, pair.getFirst()); + GroupGroupPerm p1; + if (exist.isPresent()) { + p1 = exist.get(); + if (!pair.getSecond().equals(p1.getRole())) { + responseBuilder.insertPermissionUpdate(PermissionUpdateResponse.Action.REMOVE, p1); + p1.setRole(pair.getSecond()); + groupGroupPermRepo.save(p1); + } else { + continue; + } + } else { + p1 = new GroupGroupPerm(group, pair.getFirst(), pair.getSecond()); + groupGroupPermRepo.save(p1); + } + responseBuilder.insertPermissionUpdate(PermissionUpdateResponse.Action.ADD, p1); + } + break; + case REPLACE: + responseBuilder.insertPermissionUpdates_GroupGroupPerm(PermissionUpdateResponse.Action.REMOVE, + groupGroupPermRepo.findAllByGroup(group)); + groupGroupPermRepo.deleteByGroup(group); + for (PermissionUpdateRequest.Permission p: req.getPermissions()) { + Pair pair = getGroupAndRole(p); + if (pair.getFirst() == null || pair.getSecond() == null) { + continue; + } + GroupGroupPerm p1 = new GroupGroupPerm(group, pair.getFirst(), pair.getSecond()); + groupGroupPermRepo.save(p1); + responseBuilder.insertPermissionUpdate(PermissionUpdateResponse.Action.ADD, p1); + } + break; + case REMOVE: + Set groupPerms = new HashSet<>(); + req.getPermissions().forEach(p -> { + Optional groupPerm = getGroupRepo().findByName(p.getName()); + if(! groupPerm.isPresent()) { + //throw or skip + return; + } + + groupPerms.add(p.getName()); + responseBuilder.insertPermissionUpdate(PermissionUpdateResponse.Action.REMOVE, + groupGroupPermRepo.findByGroupAndGroupPerm(group, groupPerm.get()).orElse(null)); + }); + groupGroupPermRepo.deleteByGroupAndGroupPerm_NameIn(group, groupPerms); + break; + } + return responseBuilder.getPermissionUpdateResponse(); + } + + @Override + public PermissionResponse getUserRoles() { + PermissionResponse res = PermissionResponse.getDefaultResponse(); + for (GroupUserPerm perm: groupUserPermRepo.findAllByGroup_Name(group.getName())) { + res.getPermissions().add(new PermissionResponse.Permission( + perm.getUser().getUsername(), + perm.getRole().getName(), + false + )); + } + return res; + } + + @Override + public PermissionResponse getGroupRoles() { + PermissionResponse res = PermissionResponse.getDefaultResponse(); + for (GroupGroupPerm perm: groupGroupPermRepo.findAllByGroup_Name(group.getName())) { + res.getPermissions().add(new PermissionResponse.Permission( + perm.getGroupPerm().getName(), + perm.getRole().getName(), + false + )); + } + return res; + } + + @Override + public PermissionUpdatesResponse recalculateInheritedPerms() { + //Do nothing, can't inherit permissions + return new PermissionUpdatesResponseBuilder().getPermissionUpdatesResponse(); + } + +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultOrgPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultOrgPermissionsDelegate.java index 370217058..e0db25523 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultOrgPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultOrgPermissionsDelegate.java @@ -287,7 +287,7 @@ public PermissionResponse getGroupRoles() { @Override public PermissionUpdatesResponse recalculateInheritedPerms() { //Do nothing, can't inherit permissions - return new PermissionUpdatesResponseBuilder().getPermissionUpdatesReponse(); + return new PermissionUpdatesResponseBuilder().getPermissionUpdatesResponse(); } } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultProjectPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultProjectPermissionsDelegate.java index e5beb45e9..9c83e4a84 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultProjectPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultProjectPermissionsDelegate.java @@ -420,6 +420,6 @@ public PermissionUpdatesResponse recalculateInheritedPerms() { projectGroupPermRepo.saveAll(groupPermissions.getAll()); responseBuilder.getGroups().insertPermissionUpdates_ProjectGroupPerm(PermissionUpdateResponse.Action.ADD, groupPermissions.getAll()); - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionsUpdateResponseBuilder.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionsUpdateResponseBuilder.java index bde43a17e..5847f7d49 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionsUpdateResponseBuilder.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionsUpdateResponseBuilder.java @@ -19,7 +19,7 @@ public void insertPermissionUpdate(PermissionUpdateResponse.Action action, OrgUs PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( action, v.getUser().getUsername(), v.getRole().getName(), v.getOrganization().getOrganizationId(), v.getOrganization().getOrganizationName(), - null, null, null, false); + null, null, null, null, false); doInsert(update); } @@ -33,7 +33,7 @@ public void insertPermissionUpdate(PermissionUpdateResponse.Action action, OrgGr PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( action, v.getGroup().getName(), v.getRole().getName(), v.getOrganization().getOrganizationId(), v.getOrganization().getOrganizationName(), - null, null, null, false); + null, null, null, null, false); doInsert(update); } @@ -48,7 +48,7 @@ public void insertPermissionUpdate(PermissionUpdateResponse.Action action, Proje PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( action, v.getUser().getUsername(), v.getRole().getName(), v.getProject().getOrganization().getOrganizationId(), v.getProject().getOrganization().getOrganizationName(), v.getProject().getProjectId(), v.getProject().getProjectName(), - null, v.isInherited()); + null, null, v.isInherited()); doInsert(update); } @@ -63,7 +63,7 @@ public void insertPermissionUpdate(PermissionUpdateResponse.Action action, Proje PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( action, v.getGroup().getName(), v.getRole().getName(), v.getProject().getOrganization().getOrganizationId(), v.getProject().getOrganization().getOrganizationName(), v.getProject().getProjectId(), v.getProject().getProjectName(), - null, v.isInherited()); + null, null, v.isInherited()); doInsert(update); } @@ -78,7 +78,7 @@ public void insertPermissionUpdate(PermissionUpdateResponse.Action action, Branc PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( action, v.getUser().getUsername(), v.getRole().getName(), v.getBranch().getProject().getOrganization().getOrganizationId(), v.getBranch().getProject().getOrganization().getOrganizationName(), v.getBranch().getProject().getProjectId(), - v.getBranch().getProject().getProjectName(), v.getBranch().getBranchId(), v.isInherited()); + v.getBranch().getProject().getProjectName(), v.getBranch().getBranchId(), null, v.isInherited()); doInsert(update); } @@ -93,7 +93,36 @@ public void insertPermissionUpdate(PermissionUpdateResponse.Action action, Branc PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( action, v.getGroup().getName(), v.getRole().getName(), v.getBranch().getProject().getOrganization().getOrganizationId(), v.getBranch().getProject().getOrganization().getOrganizationName(), v.getBranch().getProject().getProjectId(), - v.getBranch().getProject().getProjectName(),v.getBranch().getBranchId(), v.isInherited()); + v.getBranch().getProject().getProjectName(),v.getBranch().getBranchId(), null, v.isInherited()); doInsert(update); } + + public void insertPermissionUpdates_GroupUserPerm(PermissionUpdateResponse.Action action, Collection perms) { + perms.forEach(v -> insertPermissionUpdate(action, v)); + } + + public void insertPermissionUpdate(PermissionUpdateResponse.Action action, GroupUserPerm v) { + if(v == null) + return; + + PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( + action, v.getUser().getUsername(), v.getRole().getName(),null,null,null,null, + null, v.getGroup().getName(), false); + doInsert(update); + } + + public void insertPermissionUpdates_GroupGroupPerm(PermissionUpdateResponse.Action action, Collection perms) { + perms.forEach(v -> insertPermissionUpdate(action, v)); + } + + public void insertPermissionUpdate(PermissionUpdateResponse.Action action, GroupGroupPerm v) { + if(v == null) + return; + + PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( + action, v.getGroup().getName(), v.getRole().getName(),null,null,null,null, + null, v.getGroup().getName(), false); + doInsert(update); + } + } diff --git a/groups/src/main/java/org/openmbee/mms/groups/constants/GroupConstants.java b/groups/src/main/java/org/openmbee/mms/groups/constants/GroupConstants.java index 148773d16..3238c400c 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/constants/GroupConstants.java +++ b/groups/src/main/java/org/openmbee/mms/groups/constants/GroupConstants.java @@ -6,8 +6,14 @@ public class GroupConstants { public static final String GROUP_NOT_EMPTY = "Group is not empty"; public static final String GROUP_NOT_FOUND = "Group not found"; public static final String INVALID_ACTION = "Invalid action"; - public static final String INVALID_GROUP_NAME= "Invalid group name"; + public static final String INVALID_GROUP_NAME = "Invalid group name"; + public static final String NAME = "name"; + public static final String TYPE = "type"; public static final String NO_USERS_PROVIDED = "No users provided"; public static final String RESTRICTED_GROUP = "Restricted group"; + public static final String NO_DELETE_RESTRICTED = "Unable to delete restricted group"; + public static final String REMOTE_GROUP = "Unable to add users to remote group"; + public static final String INVALID_GROUP_TYPE= "Invalid group type"; + public static final String NO_PERMISSSION = "No permission to update group"; } diff --git a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java index 5dc3c6732..76c5168d1 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java +++ b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java @@ -1,35 +1,63 @@ package org.openmbee.mms.groups.controllers; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.ArrayList; -import java.util.Collection; +import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.config.Privileges; import org.openmbee.mms.core.dao.GroupPersistence; import org.openmbee.mms.core.dao.UserGroupsPersistence; -import org.openmbee.mms.core.exceptions.BadRequestException; -import org.openmbee.mms.core.exceptions.ConflictException; -import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.core.dao.UserPersistence; +import org.openmbee.mms.core.exceptions.*; +import org.openmbee.mms.core.objects.*; +import org.openmbee.mms.core.security.MethodSecurityService; +import org.openmbee.mms.core.services.PermissionService; import org.openmbee.mms.groups.constants.GroupConstants; import org.openmbee.mms.groups.objects.*; import org.openmbee.mms.groups.services.GroupValidationService; import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.UserJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.User; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/groups") @Tag(name = "Groups") +@Transactional public class LocalGroupsController { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + protected ObjectMapper om; + private GroupPersistence groupPersistence; - private UserGroupsPersistence userGroupsPersistence; private GroupValidationService groupValidationService; + private UserPersistence userPersistence; + private UserGroupsPersistence userGroupsPersistence; + + protected PermissionService permissionService; + + protected MethodSecurityService mss; + + public Map convertToMap(Object obj) { + return om.convertValue(obj, new TypeReference>() {}); + } @Autowired public void setGroupPersistence(GroupPersistence groupPersistence) { @@ -46,8 +74,28 @@ public void setGroupValidationService(GroupValidationService groupValidationServ this.groupValidationService = groupValidationService; } + @Autowired + public void setPermissionService(PermissionService permissionService) { + this.permissionService = permissionService; + } + + @Autowired + public void setMss(MethodSecurityService mss) { + this.mss = mss; + } + + @Autowired + public void setObjectMapper(ObjectMapper om) { + this.om = om; + } + + @Autowired + public void setUserPersistence(UserPersistence userPersistence) { + this.userPersistence = userPersistence; + } + @PutMapping("/{group}") - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) + @PreAuthorize("isAuthenticated()") @ResponseBody public void createLocalGroup(@PathVariable String group) { GroupJson groupJson = groupPersistence.findByName(group).orElse(null); @@ -65,27 +113,31 @@ public void createLocalGroup(@PathVariable String group) { } @GetMapping - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) - public GroupsResponse getAllGroups() { - Collection groups = groupPersistence.findAll(); + @PreAuthorize("isAuthenticated()") + public GroupsResponse getAllGroups( + Authentication auth) { + GroupsResponse response = new GroupsResponse(); - response.setGroups(groups.stream().map(GroupJson::getName).collect(Collectors.toList())); + Collection allGroups = groupPersistence.findAll(); + for (GroupJson group : allGroups) { + if (mss.hasOrgPrivilege(auth, group.getName(), Privileges.GROUP_READ.name(), false)) { + response.getGroups().add(group); + } + } return response; } - @GetMapping("/{group}") - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) + @GetMapping(value = "/{group}") + @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_READ', true)") public GroupResponse getGroup(@PathVariable String group) { - if(groupValidationService.isRestrictedGroup(group)) { - throw new BadRequestException(GroupConstants.RESTRICTED_GROUP); - } return new GroupResponse(groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)), userGroupsPersistence.findUsersInGroup(group).stream().map(UserJson::getUsername).collect(Collectors.toList())); } @DeleteMapping("/{group}") - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) + @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_DELETE', false)") @ResponseBody + @Transactional public void deleteLocalGroup(@PathVariable String group) { GroupJson groupJson = groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)); if (groupValidationService.canDeleteGroup(groupJson)) { @@ -96,7 +148,7 @@ public void deleteLocalGroup(@PathVariable String group) { } @PostMapping("/{group}/users") - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) + @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_EDIT', true)") public GroupUpdateResponse updateGroupUsers(@PathVariable String group, @RequestBody GroupUpdateRequest groupUpdateRequest) { @@ -139,4 +191,31 @@ public GroupUpdateResponse updateGroupUsers(@PathVariable String group, }); return response; } + + protected void handleSingleResponse(BaseResponse res) { + if (res.getRejected() != null && !res.getRejected().isEmpty()) { + List rejected = res.getRejected(); + int code = rejected.get(0).getCode(); + switch(code) { + case 304: + throw new NotModifiedException(res); + case 400: + throw new BadRequestException(res); + case 401: + throw new UnauthorizedException(res); + case 403: + throw new ForbiddenException(res); + case 404: + throw new NotFoundException(res); + case 409: + throw new ConflictException(res); + case 410: + throw new DeletedException(res); + case 500: + throw new InternalErrorException(res); + default: + break; + } + } + } } diff --git a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupUsersResponse.java b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupUsersResponse.java new file mode 100644 index 000000000..16d7f8ff8 --- /dev/null +++ b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupUsersResponse.java @@ -0,0 +1,26 @@ +package org.openmbee.mms.groups.objects; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.openmbee.mms.core.objects.BaseResponse; +import org.openmbee.mms.json.GroupJson; + +import java.util.ArrayList; +import java.util.List; + +public class GroupUsersResponse extends BaseResponse { + + @Schema(required = true) + private List users; + + public GroupUsersResponse() { + this.users = new ArrayList<>(); + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } +} diff --git a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsRequest.java b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsRequest.java new file mode 100644 index 000000000..7d902e33d --- /dev/null +++ b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsRequest.java @@ -0,0 +1,21 @@ +package org.openmbee.mms.groups.objects; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.openmbee.mms.json.GroupJson; + +import java.util.List; + +public class GroupsRequest { + + @Schema(required = true) + private List groups; + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + +} \ No newline at end of file diff --git a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsResponse.java b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsResponse.java index bc13bcd19..80e5fc6f2 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsResponse.java +++ b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsResponse.java @@ -1,19 +1,26 @@ package org.openmbee.mms.groups.objects; -import io.swagger.v3.oas.annotations.media.Schema; +import org.openmbee.mms.core.objects.BaseResponse; + +import org.openmbee.mms.json.GroupJson; +import java.util.ArrayList; import java.util.List; -public class GroupsResponse { +public class GroupsResponse extends BaseResponse { + + private List groups; - @Schema(required = true) - private List groups; + public GroupsResponse() { + this.groups = new ArrayList<>(); + } - public List getGroups() { - return groups; + public List getGroups() { + return this.groups; } - public void setGroups(List groups) { + public void setGroups(List groups) { this.groups = groups; } + } diff --git a/json/src/main/java/org/openmbee/mms/json/GroupJson.java b/json/src/main/java/org/openmbee/mms/json/GroupJson.java index f22a57a1b..2535012d2 100644 --- a/json/src/main/java/org/openmbee/mms/json/GroupJson.java +++ b/json/src/main/java/org/openmbee/mms/json/GroupJson.java @@ -3,9 +3,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.swagger.v3.oas.annotations.media.Schema; -@JsonIgnoreProperties({BaseJson.COMMITID, BaseJson.REFID, BaseJson.PROJECTID, BaseJson.TYPE, - BaseJson.ID, "empty"}) -@Schema(name = "Group", requiredProperties = {BaseJson.NAME}) +@JsonIgnoreProperties({BaseJson.COMMITID, BaseJson.REFID, BaseJson.PROJECTID, BaseJson.ID, "empty"}) +@Schema(name = "Group", requiredProperties = {BaseJson.NAME, BaseJson.TYPE}) public class GroupJson extends BaseJson { } diff --git a/ldap/ldap.gradle b/ldap/ldap.gradle index 588391d59..bb76b1ccb 100644 --- a/ldap/ldap.gradle +++ b/ldap/ldap.gradle @@ -1,6 +1,6 @@ dependencies { implementation project(':rdb') - + implementation project(':users') implementation commonDependencies.'spring-security-ldap' } diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java similarity index 93% rename from ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java rename to ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java index 9bb98618d..23b901670 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.ldap; +package org.openmbee.mms.ldap.config; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java similarity index 78% rename from ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java rename to ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java index fa3666b3b..d239d87d8 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java @@ -1,11 +1,16 @@ -package org.openmbee.mms.ldap; +package org.openmbee.mms.ldap.config; import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.dao.GroupPersistence; import org.openmbee.mms.core.dao.UserGroupsPersistence; -import org.openmbee.mms.core.dao.UserPersistence; +import org.openmbee.mms.core.exceptions.ForbiddenException; import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.ldap.security.LdapUsersDetailsService; +import org.openmbee.mms.users.security.AbstractUsersDetailsService; +import org.openmbee.mms.users.security.UsersDetails; +import org.openmbee.mms.users.security.UsersDetailsService; + import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.*; @@ -28,6 +33,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.SpringSecurityLdapTemplate; import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; @@ -42,6 +48,31 @@ public class LdapSecurityConfig { private static Logger logger = LoggerFactory.getLogger(LdapSecurityConfig.class); + private LdapUsersDetailsService userDetailsService; + + private GroupPersistence groupPersistence; + + private UserGroupsPersistence userGroupsPersistence; + + @Autowired + public void setGroupPersistence(GroupPersistence groupPersistence) { + this.groupPersistence = groupPersistence; + } + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; + } + + + @Autowired + public void setUserDetailsService(LdapUsersDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + + + @Value("${ldap.ad.enabled:false}") private Boolean adEnabled; @@ -63,21 +94,6 @@ public class LdapSecurityConfig { @Value("#{'${ldap.user.dn.pattern:uid={0}}'.split(';')}") private List userDnPattern; - @Value("${ldap.user.attributes.username:uid}") - private String userAttributesUsername; - - @Value("${ldap.user.attributes.firstname:givenname}") - private String userAttributesFirstName; - - @Value("${ldap.user.attributes.lastname:sn}") - private String userAttributesLastName; - - @Value("${ldap.user.attributes.email:mail}") - private String userAttributesEmail; - - @Value("${ldap.user.attributes.update:24}") - private int userAttributesUpdate; - @Value("${ldap.group.search.base:#{''}}") private String groupSearchBase; @@ -92,24 +108,6 @@ public class LdapSecurityConfig { @Value("${ldap.user.search.filter:(uid={0})}") private String userSearchFilter; - private UserPersistence userPersistence; - private GroupPersistence groupPersistence; - private UserGroupsPersistence userGroupsPersistence; - - @Autowired - public void setUserPersistence(UserPersistence userPersistence) { - this.userPersistence = userPersistence; - } - - @Autowired - public void setGroupPersistence(GroupPersistence groupPersistence) { - this.groupPersistence = groupPersistence; - } - - @Autowired - public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { - this.userGroupsPersistence = userGroupsPersistence; - } @Autowired public void configureLdapAuth(AuthenticationManagerBuilder auth, @@ -159,20 +157,16 @@ private CustomLdapAuthoritiesPopulator(BaseLdapPathContextSource ldapContextSour public Collection getGrantedAuthorities( DirContextOperations userData, String username) { logger.debug("Populating authorities using LDAP"); - Optional userOptional = userPersistence.findByUsername(username); - - if (userOptional.isEmpty()) { + UserJson user = new UserJson(); + try { + UsersDetails userDetails = userDetailsService.loadUserByUsername(username); + user = userDetails.getUser(); + } catch (UsernameNotFoundException e) { logger.info("No user record for {} in the userRepository, creating...", userData.getDn()); - UserJson newUser = createLdapUser(userData); - userOptional = Optional.of(newUser); + user = userDetailsService.register(userData); } - UserJson user = userOptional.get(); - if (user.getModified() != null && Instant.parse(user.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { - saveLdapUser(userData, user); - } - user.setPassword(null); - + user = userDetailsService.update(userData, user); StringBuilder userDnBuilder = new StringBuilder(); userDnBuilder.append(userData.getDn().toString()); if (providerBase != null && !providerBase.isEmpty()) { @@ -193,21 +187,18 @@ public Collection getGrantedAuthorities( andFilter.and(groupsFilter); andFilter.and(orFilter); - String filter = andFilter.encode(); - logger.debug("Searching LDAP with filter: {}", filter); - + String filter = andFilter.encode(); + logger.debug("Searching LDAP with filter: {}", filter); Set memberGroups = ldapTemplate .searchForSingleAttributeValues(groupSearchBase, filter, new Object[]{""}, groupRoleAttribute); logger.debug("LDAP search result: {}", Arrays.toString(memberGroups.toArray())); - - userPersistence.save(user); //Add groups to user Set addGroups = new HashSet<>(); for (String memberGroup : memberGroups) { Optional group = groupPersistence.findByName(memberGroup); - group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), user.getUsername())); + group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), username)); group.ifPresent(addGroups::add); } @@ -270,33 +261,7 @@ public LdapContextSource contextSource() { return contextSource; } - private UserJson saveLdapUser(DirContextOperations userData, UserJson saveUser) { - if (saveUser.getEmail() == null || - !saveUser.getEmail().equals(userData.getStringAttribute(userAttributesEmail)) - ) { - saveUser.setEmail(userData.getStringAttribute(userAttributesEmail)); - } - if (saveUser.getFirstName() == null || - !saveUser.getFirstName().equals(userData.getStringAttribute(userAttributesFirstName)) - ) { - saveUser.setFirstName(userData.getStringAttribute(userAttributesFirstName)); - } - if (saveUser.getLastName() == null || - !saveUser.getLastName().equals(userData.getStringAttribute(userAttributesLastName)) - ) { - saveUser.setLastName(userData.getStringAttribute(userAttributesLastName)); - } + - return saveUser; - } - - private UserJson createLdapUser(DirContextOperations userData) { - String username = userData.getStringAttribute(userAttributesUsername); - logger.debug("Creating user for {} using LDAP", username); - UserJson user = saveLdapUser(userData, new UserJson()); - user.setUsername(username); - user.setEnabled(true); - user.setAdmin(false); - return userPersistence.save(user); - } + } \ No newline at end of file diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java new file mode 100644 index 000000000..57afe8ae6 --- /dev/null +++ b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java @@ -0,0 +1,86 @@ +package org.openmbee.mms.ldap.security; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import org.openmbee.mms.core.exceptions.ForbiddenException; +import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.users.security.AbstractUsersDetailsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.stereotype.Service; + +@Service +public class LdapUsersDetailsService extends AbstractUsersDetailsService { + + private static Logger logger = LoggerFactory.getLogger(LdapUsersDetailsService.class); + + @Value("${ldap.provider.base:#{null}}") + private String providerBase; + + @Value("${ldap.user.search.filter:(uid={0})}") + private String userSearchFilter; + + @Value("${ldap.user.attributes.username:uid}") + private String userAttributesUsername; + + @Value("${ldap.user.attributes.firstname:givenname}") + private String userAttributesFirstName; + + @Value("${ldap.user.attributes.lastname:sn}") + private String userAttributesLastName; + + @Value("${ldap.user.attributes.email:mail}") + private String userAttributesEmail; + + @Value("${ldap.user.attributes.update:24}") + private int userAttributesUpdate; + + @Value("${ldap.group.role.attribute:cn}") + private String groupRoleAttribute; + + @Value("${ldap.group.search.filter:(uniqueMember={0})}") + private String groupSearchFilter; + + public UserJson register(DirContextOperations userData) { + String username = userData.getStringAttribute(userAttributesUsername); + logger.debug("Creating user for {} using LDAP", username); + UserJson user = update(userData, new UserJson()); + user.setUsername(username); + user.setEnabled(true); + user.setAdmin(false); + return saveUser(user); + } + + public void changeUserPassword(String username, String password, boolean asAdmin) { + throw new ForbiddenException("Cannot change or set passwords for external users."); + } + + public UserJson update(DirContextOperations userData, UserJson saveUser) { + if (saveUser.getModified() != null && Instant.parse(saveUser.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { + if (saveUser.getEmail() == null || + !saveUser.getEmail().equals(userData.getStringAttribute(userAttributesEmail)) + ) { + saveUser.setEmail(userData.getStringAttribute(userAttributesEmail)); + } + if (saveUser.getFirstName() == null || + !saveUser.getFirstName().equals(userData.getStringAttribute(userAttributesFirstName)) + ) { + saveUser.setFirstName(userData.getStringAttribute(userAttributesFirstName)); + } + if (saveUser.getLastName() == null || + !saveUser.getLastName().equals(userData.getStringAttribute(userAttributesLastName)) + ) { + saveUser.setLastName(userData.getStringAttribute(userAttributesLastName)); + } + + } + + saveUser.setPassword(null); + + return saveUser(saveUser); + } + +} diff --git a/localuser/README.rst b/localauth/README.rst similarity index 92% rename from localuser/README.rst rename to localauth/README.rst index ea4c195b9..0ee94d539 100644 --- a/localuser/README.rst +++ b/localauth/README.rst @@ -1,6 +1,6 @@ -.. _localuser: +.. _localauth: -LocalUser +Localauth ------------------------ Adds endpoint for adding local users, admins and changing password, and local authentication provider diff --git a/localauth/localauth.gradle b/localauth/localauth.gradle new file mode 100644 index 000000000..31e389138 --- /dev/null +++ b/localauth/localauth.gradle @@ -0,0 +1,4 @@ +dependencies { + implementation project(':rdb') + implementation project(':users') +} diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/config/AuthProviderConfig.java b/localauth/src/main/java/org/openmbee/mms/localauth/config/AuthProviderConfig.java similarity index 85% rename from localuser/src/main/java/org/openmbee/mms/localuser/config/AuthProviderConfig.java rename to localauth/src/main/java/org/openmbee/mms/localauth/config/AuthProviderConfig.java index eb8b08e4e..f9a3151e7 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/config/AuthProviderConfig.java +++ b/localauth/src/main/java/org/openmbee/mms/localauth/config/AuthProviderConfig.java @@ -1,7 +1,7 @@ -package org.openmbee.mms.localuser.config; +package org.openmbee.mms.localauth.config; -import org.openmbee.mms.localuser.security.UserCreateRequest; -import org.openmbee.mms.localuser.security.UserDetailsServiceImpl; +import org.openmbee.mms.localauth.security.LocalUsersDetailsService; +import org.openmbee.mms.users.objects.UserCreateRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -17,7 +17,7 @@ public class AuthProviderConfig { private static final Logger logger = LoggerFactory.getLogger(AuthProviderConfig.class); - private UserDetailsServiceImpl userDetailsService; + private LocalUsersDetailsService userDetailsService; private PasswordEncoder passwordEncoder; @Value("${mms.admin.username}") @@ -26,7 +26,7 @@ public class AuthProviderConfig { private String adminPassword; @Autowired - public void setUserDetailsService(UserDetailsServiceImpl userDetailsService) { + public void setUserDetailsService(LocalUsersDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @@ -53,4 +53,4 @@ public DaoAuthenticationProvider daoAuthenticationProvider() { authProvider.setPasswordEncoder(passwordEncoder); return authProvider; } -} +} \ No newline at end of file diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/config/LocalUserSecurityConfig.java b/localauth/src/main/java/org/openmbee/mms/localauth/config/LocalAuthSecurityConfig.java similarity index 88% rename from localuser/src/main/java/org/openmbee/mms/localuser/config/LocalUserSecurityConfig.java rename to localauth/src/main/java/org/openmbee/mms/localauth/config/LocalAuthSecurityConfig.java index 587caa8cd..2b422ea62 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/config/LocalUserSecurityConfig.java +++ b/localauth/src/main/java/org/openmbee/mms/localauth/config/LocalAuthSecurityConfig.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.localuser.config; +package org.openmbee.mms.localauth.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,9 +10,9 @@ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) -public class LocalUserSecurityConfig { +public class LocalAuthSecurityConfig { - private static Logger logger = LoggerFactory.getLogger(LocalUserSecurityConfig.class); + private static Logger logger = LoggerFactory.getLogger(LocalAuthSecurityConfig.class); @Autowired public void configureDaoAuth(AuthenticationManagerBuilder auth, diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/config/PasswordEncoderConfig.java b/localauth/src/main/java/org/openmbee/mms/localauth/config/PasswordEncoderConfig.java similarity index 94% rename from localuser/src/main/java/org/openmbee/mms/localuser/config/PasswordEncoderConfig.java rename to localauth/src/main/java/org/openmbee/mms/localauth/config/PasswordEncoderConfig.java index efc6850d4..c82746fa7 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/config/PasswordEncoderConfig.java +++ b/localauth/src/main/java/org/openmbee/mms/localauth/config/PasswordEncoderConfig.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.localuser.config; +package org.openmbee.mms.localauth.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java b/localauth/src/main/java/org/openmbee/mms/localauth/config/UserPasswordRulesConfig.java similarity index 92% rename from localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java rename to localauth/src/main/java/org/openmbee/mms/localauth/config/UserPasswordRulesConfig.java index 05cf7cffc..d7a623821 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java +++ b/localauth/src/main/java/org/openmbee/mms/localauth/config/UserPasswordRulesConfig.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.localuser.config; +package org.openmbee.mms.localauth.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java b/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java similarity index 58% rename from localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java rename to localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java index 893692b9a..3c560da2c 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java +++ b/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.localuser.security; +package org.openmbee.mms.localauth.security; import java.util.Collection; import java.util.Optional; @@ -7,7 +7,10 @@ import org.openmbee.mms.core.dao.UserPersistence; import org.openmbee.mms.core.exceptions.ForbiddenException; import org.openmbee.mms.json.UserJson; -import org.openmbee.mms.localuser.config.UserPasswordRulesConfig; +import org.openmbee.mms.localauth.config.UserPasswordRulesConfig; +import org.openmbee.mms.users.objects.UserCreateRequest; +import org.openmbee.mms.users.security.AbstractUsersDetailsService; +import org.openmbee.mms.users.security.DefaultUsersDetails; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -15,23 +18,11 @@ import org.springframework.stereotype.Service; @Service -public class UserDetailsServiceImpl implements UserDetailsService { +public class LocalUsersDetailsService extends AbstractUsersDetailsService { - private UserPersistence userPersistence; - private UserGroupsPersistence userGroupsPersistence; private PasswordEncoder passwordEncoder; private UserPasswordRulesConfig userPasswordRulesConfig; - @Autowired - public void setUserPersistence(UserPersistence userPersistence) { - this.userPersistence = userPersistence; - } - - @Autowired - public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { - this.userGroupsPersistence = userGroupsPersistence; - } - @Autowired public void setPasswordEncoder(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; @@ -43,34 +34,31 @@ public void setUserPasswordRulesConfig(UserPasswordRulesConfig userPasswordRules } @Override - public UserDetailsImpl loadUserByUsername(String username) throws UsernameNotFoundException { - Optional user = userPersistence.findByUsername(username); + public DefaultUsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Optional user = getUserPersistence().findByUsername(username); if (user.isEmpty()) { throw new UsernameNotFoundException( String.format("No user found with username '%s'.", username)); } - return new UserDetailsImpl(user.get(), userGroupsPersistence.findGroupsAssignedToUser(username)); - } - - public Collection getUsers() { - return userPersistence.findAll(); + return new DefaultUsersDetails(user.get(), getUserGroupsPersistence().findGroupsAssignedToUser(username)); } - + + public UserJson register(UserCreateRequest req) { UserJson user = new UserJson(); user.setUsername(req.getUsername()); user.setEmail(req.getEmail()); - user.setFirstName(req.getFirstname()); - user.setLastName(req.getLastname()); + user.setFirstName(req.getFirstName()); + user.setLastName(req.getLastName()); user.setPassword(encodePassword(req.getPassword())); user.setEnabled(true); user.setAdmin(req.isAdmin()); - return userPersistence.save(user); + return saveUser(user); } public void changeUserPassword(String username, String password, boolean asAdmin) { - Optional userOptional = userPersistence.findByUsername(username); + Optional userOptional = getUserPersistence().findByUsername(username); if(userOptional.isEmpty()) { throw new UsernameNotFoundException( String.format("No user found with username '%s'.", username)); @@ -84,10 +72,34 @@ public void changeUserPassword(String username, String password, boolean asAdmin //TODO password strength test? user.setPassword(encodePassword(password)); - userPersistence.save(user); + saveUser(user); + } + + public UserJson update(UserCreateRequest req, UserJson user) { + if (req.getEmail() != null && + !user.getEmail().equals(req.getEmail()) + ) { + user.setEmail(req.getEmail()); + } + if (req.getFirstName() != null && + !user.getFirstName().equals(req.getFirstName()) + ) { + user.setFirstName(req.getFirstName()); + } + if (req.getLastName() != null && + !user.getLastName().equals(req.getLastName()) + ) { + user.setLastName(req.getLastName()); + } + if (req.isEnabled() != null && user.isEnabled() != req.isEnabled()) + + if (req.getType() != null) { + user.setType(req.getType()); + } + return saveUser(user); } private String encodePassword(String password) { return (password != null && !password.isBlank()) ? passwordEncoder.encode(password) : null; } -} +} \ No newline at end of file diff --git a/localuser/src/main/resources/application.properties.example b/localauth/src/main/resources/application.properties.example similarity index 100% rename from localuser/src/main/resources/application.properties.example rename to localauth/src/main/resources/application.properties.example diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/controllers/LocalUserController.java b/localuser/src/main/java/org/openmbee/mms/localuser/controllers/LocalUserController.java deleted file mode 100644 index 858ea4a1f..000000000 --- a/localuser/src/main/java/org/openmbee/mms/localuser/controllers/LocalUserController.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.openmbee.mms.localuser.controllers; - -import io.swagger.v3.oas.annotations.tags.Tag; -import org.openmbee.mms.core.config.AuthorizationConstants; -import org.openmbee.mms.core.exceptions.BadRequestException; -import org.openmbee.mms.core.exceptions.NotFoundException; -import org.openmbee.mms.core.exceptions.UnauthorizedException; -import org.openmbee.mms.core.utils.AuthenticationUtils; -import org.openmbee.mms.json.UserJson; -import org.openmbee.mms.localuser.security.UserCreateRequest; -import org.openmbee.mms.localuser.security.UserDetailsServiceImpl; -import org.openmbee.mms.localuser.security.UsersResponse; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.*; - -import java.util.ArrayList; -import java.util.Collection; - -@RestController -@Tag(name = "Auth") -public class LocalUserController { - - private UserDetailsServiceImpl userDetailsService; - - @Autowired - public LocalUserController(UserDetailsServiceImpl userDetailsService) { - this.userDetailsService = userDetailsService; - } - - @PostMapping(value = "/user", consumes = MediaType.APPLICATION_JSON_VALUE) - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) - public UserCreateRequest createUser(@RequestBody UserCreateRequest req) { - try { - userDetailsService.loadUserByUsername(req.getUsername()); - } catch (UsernameNotFoundException e) { - userDetailsService.register(req); - return req; - } - throw new BadRequestException("User already exists"); - } - - @GetMapping(value = "/users") - @PreAuthorize("isAuthenticated()") - public UsersResponse getUsers(@RequestParam(required = false) String user) { - UsersResponse res = new UsersResponse(); - Collection users = new ArrayList<>(); - if (user != null) { - users.add(userDetailsService.loadUserByUsername(user).getUser()); - } else { - users = userDetailsService.getUsers(); - } - res.setUsers(users); - return res; - } - - @PostMapping(value = "/password", consumes = MediaType.APPLICATION_JSON_VALUE) - @PreAuthorize("isAuthenticated()") - public Object updatePassword(@RequestBody UserCreateRequest req, - Authentication auth) { - final String requester = auth.getName(); - final boolean requesterAdmin = AuthenticationUtils - .hasGroup(auth, AuthorizationConstants.MMSADMIN); - - try { - if (requesterAdmin || requester.equals(req.getUsername())) { - userDetailsService.changeUserPassword(req.getUsername(), req.getPassword(), requesterAdmin); - } else { - throw new UnauthorizedException("Not authorized"); - } - - } catch (UsernameNotFoundException e) { - if (requesterAdmin) { - throw new NotFoundException("User not found"); - } else { - throw new UnauthorizedException("Not authorized"); - } - } - return ""; - } - -} diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsImpl.java b/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsImpl.java deleted file mode 100644 index 038e5d4da..000000000 --- a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsImpl.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.openmbee.mms.localuser.security; - -import java.util.ArrayList; -import java.util.Collection; - -import org.openmbee.mms.core.config.AuthorizationConstants; -import org.openmbee.mms.json.GroupJson; -import org.openmbee.mms.json.UserJson; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -public class UserDetailsImpl implements UserDetails { - - private final UserJson user; - private final Collection groups; - - public UserDetailsImpl(UserJson user, Collection groups) { - this.user = user; - this.groups = groups; - } - - @Override - public Collection getAuthorities() { - - Collection authorities = new ArrayList<>(); - - if (groups != null) { - for (GroupJson group : groups) { - authorities.add(new SimpleGrantedAuthority(group.getName())); - } - } - if (Boolean.TRUE.equals(user.isAdmin())) { - authorities.add(new SimpleGrantedAuthority(AuthorizationConstants.MMSADMIN)); - } - authorities.add(new SimpleGrantedAuthority(AuthorizationConstants.EVERYONE)); - return authorities; - } - - @Override - public String getPassword() { - return user.getPassword(); - } - - @Override - public String getUsername() { - return user.getUsername(); - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return user.isEnabled(); - } - - public UserJson getUser() { - return user; - } - -} diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java index 9790620ca..7b6ba925f 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java +++ b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java @@ -42,7 +42,7 @@ public PermissionUpdatesResponse updateOrgPermissions( if (req.getPublic() != null) { responseBuilder.setPublic(permissionService.setOrgPublic(req.getPublic(), orgId)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @PostMapping(value = "/projects/{projectId}/permissions", consumes = MediaType.APPLICATION_JSON_VALUE) @@ -65,7 +65,7 @@ public PermissionUpdatesResponse updateProjectPermissions( if (req.getInherit() != null) { responseBuilder.insert(permissionService.setProjectInherit(req.getInherit(), projectId)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @PostMapping(value = "/projects/{projectId}/refs/{refId}/permissions", consumes = MediaType.APPLICATION_JSON_VALUE) @@ -86,7 +86,27 @@ public PermissionUpdatesResponse updateBranchPermissions( if (req.getInherit() != null) { responseBuilder.insert(permissionService.setBranchInherit(req.getInherit(), projectId, refId)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); + } + + @PostMapping(value = "/groups/{group}/permissions", consumes = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_UPDATE_PERMISSIONS', false)") + public PermissionUpdatesResponse updateGroupPermissions( + @PathVariable String group, + @RequestBody PermissionsRequest req) { + + PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); + + if (req.getGroups() != null) { + responseBuilder.insert(permissionService.updateGroupGroupPerms(req.getGroups(), group)); + } + if (req.getUsers() != null) { + responseBuilder.insert(permissionService.updateGroupUserPerms(req.getUsers(), group)); + } + if (req.getPublic() != null) { + responseBuilder.setPublic(permissionService.setGroupPublic(req.getPublic(), group)); + } + return responseBuilder.getPermissionUpdatesResponse(); } @GetMapping(value = "/orgs/{orgId}/permissions") @@ -127,4 +147,17 @@ public PermissionsResponse getBranchPermissions( res.setInherit(permissionService.isBranchInherit(projectId, refId)); return res; } + + @GetMapping(value = "/groups/{group}/permissions") + @PreAuthorize("@mss.hasGroupPrivilege(authentication, #groupName, 'GROUP_READ_PERMISSIONS', true)") + public PermissionsResponse getGroupPermissions( + @PathVariable String group) { + + PermissionsResponse res = new PermissionsResponse(); + res.setGroups(permissionService.getGroupGroupRoles(group)); + res.setUsers(permissionService.getGroupUserRoles(group)); + res.setPublic(permissionService.isGroupPublic(group)); + return res; + } + } diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsLookupController.java b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsLookupController.java index 1354ce64d..11c86375d 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsLookupController.java +++ b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsLookupController.java @@ -51,6 +51,9 @@ public PermissionLookupResponse lookupPermissions( case BRANCH: result = mss.hasBranchPrivilege(auth, lookup.getProjectId(), lookup.getRefId(), pri, anon); break; + case GROUP: + result = mss.hasGroupPrivilege(auth, lookup.getGroupName(), pri, anon); + break; default: result = false; } diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java b/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java new file mode 100644 index 000000000..ecae6eb24 --- /dev/null +++ b/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java @@ -0,0 +1,42 @@ +package org.openmbee.mms.permissions.delegation; + +import org.openmbee.mms.core.delegation.PermissionsDelegate; +import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; +import org.openmbee.mms.data.domains.global.Branch; +import org.openmbee.mms.data.domains.global.Group; +import org.openmbee.mms.data.domains.global.Organization; +import org.openmbee.mms.data.domains.global.Project; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; + +public class DefaultPermissionsDelegateFactory implements PermissionsDelegateFactory { + + @Autowired + ApplicationContext applicationContext; + + @Override + public PermissionsDelegate getPermissionsDelegate(Project project) { + return autowire(new DefaultProjectPermissionsDelegate(project)); + } + + @Override + public PermissionsDelegate getPermissionsDelegate(Organization organization) { + return autowire(new DefaultOrgPermissionsDelegate(organization)); + } + + @Override + public PermissionsDelegate getPermissionsDelegate(Branch branch) { + return autowire(new DefaultBranchPermissionsDelegate(branch)); + } + + @Override + public PermissionsDelegate getPermissionsDelegate(Group group) { + return autowire(new DefaultGroupPermissionsDelegate(group)); + } + + private PermissionsDelegate autowire(PermissionsDelegate permissionsDelegate) { + applicationContext.getAutowireCapableBeanFactory().autowireBean(permissionsDelegate); + return permissionsDelegate; + } + +} diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/objects/PermissionLookup.java b/permissions/src/main/java/org/openmbee/mms/permissions/objects/PermissionLookup.java index 091d7bdb1..6fb7d4b95 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/objects/PermissionLookup.java +++ b/permissions/src/main/java/org/openmbee/mms/permissions/objects/PermissionLookup.java @@ -4,11 +4,12 @@ public class PermissionLookup { - public enum Type {ORG, PROJECT, BRANCH;} + public enum Type {ORG, PROJECT, BRANCH, GROUP;} private Type type; private String orgId; private String projectId; private String refId; + private String groupName; private Privileges privilege; private boolean allowAnonIfPublic; private boolean hasPrivilege; @@ -53,6 +54,10 @@ public void setRefId(String refId) { this.refId = refId; } + public String getGroupName() { return groupName; } + + public void setGroupName(String groupName) { this.groupName = groupName; } + public Privileges getPrivilege() { return privilege; } diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupGroupPermRepository.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupGroupPermRepository.java new file mode 100644 index 000000000..26a9d65cc --- /dev/null +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupGroupPermRepository.java @@ -0,0 +1,30 @@ +package org.openmbee.mms.rdb.repositories; + +import org.openmbee.mms.data.domains.global.Group; +import org.openmbee.mms.data.domains.global.GroupGroupPerm; +import org.openmbee.mms.data.domains.global.Role; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +@Repository +public interface GroupGroupPermRepository extends JpaRepository { + + List findAllByGroup(Group group); + + List findAllByGroup_Name(String group); + + Optional findByGroupAndGroupPerm( Group group, Group groupPerm); + + List findAllByGroupAndRole_Name(Group group, String r); + + boolean existsByGroupAndGroupPerm_NameInAndRoleIn(Group group, Set user, Set roles); + + void deleteByGroupAndGroupPerm_NameIn(Group group, Set groups); + + void deleteByGroup(Group group); + +} diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupUserPermRepository.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupUserPermRepository.java new file mode 100644 index 000000000..d9a594344 --- /dev/null +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupUserPermRepository.java @@ -0,0 +1,29 @@ +package org.openmbee.mms.rdb.repositories; + +import org.openmbee.mms.data.domains.global.*; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +@Repository +public interface GroupUserPermRepository extends JpaRepository { + + List findAllByGroup(Group group); + + List findAllByGroup_Name(String groupName); + + Optional findByGroupAndUser(Group b, User u); + + List findAllByGroupAndRole_Name(Group group, String r); + + List findAllByUser_Username(String username); + + boolean existsByGroupAndUser_UsernameAndRoleIn(Group group, String user, Set roles); + + void deleteByGroupAndUser_UsernameIn(Group group, Set users); + + void deleteByGroup(Group group); +} diff --git a/search/src/main/java/org/openmbee/mms/search/objects/BasicSearchRequest.java b/search/src/main/java/org/openmbee/mms/search/objects/BasicSearchRequest.java index e432b05a1..2ecd21512 100644 --- a/search/src/main/java/org/openmbee/mms/search/objects/BasicSearchRequest.java +++ b/search/src/main/java/org/openmbee/mms/search/objects/BasicSearchRequest.java @@ -40,4 +40,5 @@ public Integer getSize() { public void setSize(Integer size) { this.size = size; } + } diff --git a/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloudEndpoints.java b/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloudEndpoints.java index 05c84cc71..7d5c06582 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloudEndpoints.java +++ b/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloudEndpoints.java @@ -2,6 +2,7 @@ public enum TeamworkCloudEndpoints { LOGIN("login"), + GETUSER("admin/users/%s"), GETROLESID("resources/%s/roles"), GETPROJECTUSERS("resources/%s/roles/%s/users"); diff --git a/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactory.java b/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactory.java index f25d9a0f0..fb96f7d8b 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactory.java +++ b/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactory.java @@ -6,6 +6,7 @@ import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.json.RefJson; +import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.twc.TeamworkCloud; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.twc.config.TwcConfig; @@ -90,6 +91,16 @@ public PermissionsDelegate getPermissionsDelegate(RefJson branch) { return null; } + @Override + public PermissionsDelegate getPermissionsDelegate(GroupJson group) { + if(!twcConfig.isUseAuthDelegation()) { + return null; + } + + //Do nothing group permissions are handled by TWC + return null; + } + private PermissionsDelegate autowire(PermissionsDelegate permissionsDelegate) { applicationContext.getAutowireCapableBeanFactory().autowireBean(permissionsDelegate); return permissionsDelegate; diff --git a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java index f01284441..6712abc96 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java +++ b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java @@ -3,33 +3,25 @@ import org.openmbee.mms.core.dao.UserGroupsPersistence; import org.openmbee.mms.core.dao.UserPersistence; import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.twc.config.TwcConfig; +import org.openmbee.mms.twc.exceptions.TwcConfigurationException; +import org.openmbee.mms.twc.utilities.AdminUtils; +import org.openmbee.mms.users.security.AbstractUsersDetailsService; +import org.openmbee.mms.users.security.DefaultUsersDetails; +import org.openmbee.mms.users.security.UsersDetails; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.http.HttpStatus; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import java.util.Optional; +import java.util.*; @Service -public class TwcUserDetailsService implements UserDetailsService { - - private UserPersistence userPersistence; - private UserGroupsPersistence userGroupsPersistence; - - @Autowired - public void setUserPersistence(UserPersistence userPersistence) { - this.userPersistence = userPersistence; - } - - @Autowired - public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { - this.userGroupsPersistence = userGroupsPersistence; - } +public class TwcUserDetailsService extends AbstractUsersDetailsService { @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Optional userOptional = userPersistence.findByUsername(username); + public UsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Optional userOptional = getUserPersistence().findByUsername(username); UserJson user; if (userOptional.isEmpty()) { @@ -37,7 +29,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx } else { user = userOptional.get(); } - return new TwcUserDetails(user, userGroupsPersistence.findGroupsAssignedToUser(username)); + return new DefaultUsersDetails(user, getUserGroupsPersistence().findGroupsAssignedToUser(username)); } public UserJson addUser(String username) { @@ -45,7 +37,13 @@ public UserJson addUser(String username) { user.setUsername(username); //TODO: fill in user details from TWC user.setEnabled(true); - return userPersistence.save(user); + + return saveUser(user); + } + + public void changeUserPassword(String username, String password, boolean asAdmin) { + throw new TwcConfigurationException(HttpStatus.BAD_REQUEST, + "Cannot Modify Password. Users for this server are controlled by Teamwork Cloud"); } } diff --git a/twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java b/twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java new file mode 100644 index 000000000..c074edf77 --- /dev/null +++ b/twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java @@ -0,0 +1,43 @@ +package org.openmbee.mms.twc.utilities; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.openmbee.mms.twc.TeamworkCloud; +import org.openmbee.mms.twc.TeamworkCloudEndpoints; +import org.springframework.beans.factory.annotation.Autowired; +import org.openmbee.mms.data.domains.global.Group; +import org.openmbee.mms.data.domains.global.User; +import org.springframework.http.ResponseEntity; + +import java.util.List; +import java.util.Optional; + +public class AdminUtils { + private RestUtils restUtils; + + private JsonUtils jsonUtils; + + @Autowired + public void setRestUtils(RestUtils restUtils) { + this.restUtils = restUtils; + } + + @Autowired + public void setJsonUtils(JsonUtils jsonUtils) { + this.jsonUtils = jsonUtils; + } + + public AdminUtils() { + + }; + + public JSONObject getUserByUsername(String username, TeamworkCloud twc) { + ResponseEntity respEntity = restUtils.getRestResponse( + TeamworkCloudEndpoints.GETUSER.buildUrl(twc, username), twc); + + if (respEntity == null || respEntity.getBody() == null) + return null; + + return jsonUtils.parseStringtoJsonObject(respEntity.getBody()); + } +} diff --git a/twc/src/main/java/org/openmbee/mms/twc/utilities/JsonUtils.java b/twc/src/main/java/org/openmbee/mms/twc/utilities/JsonUtils.java index a0334a3b6..9b0d8302e 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/utilities/JsonUtils.java +++ b/twc/src/main/java/org/openmbee/mms/twc/utilities/JsonUtils.java @@ -1,6 +1,7 @@ package org.openmbee.mms.twc.utilities; import org.json.JSONArray; +import org.json.JSONObject; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -34,4 +35,14 @@ public List convertJsonArrayToStringArray(JSONArray jsonArray) { return strList; } + public JSONObject parseStringtoJsonObject(String restResponse) { + JSONObject jsonObj = null; + + jsonObj = new JSONObject(restResponse); + if (jsonObj.length() > 0) { + return null; + } + return jsonObj; + } + } diff --git a/twc/src/main/java/org/openmbee/mms/twc/utilities/RestUtils.java b/twc/src/main/java/org/openmbee/mms/twc/utilities/RestUtils.java index a84fc5070..687dbe661 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/utilities/RestUtils.java +++ b/twc/src/main/java/org/openmbee/mms/twc/utilities/RestUtils.java @@ -1,6 +1,9 @@ package org.openmbee.mms.twc.utilities; +import org.openmbee.mms.twc.TeamworkCloud; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.codec.Base64; import org.springframework.web.client.RestTemplate; @@ -48,4 +51,27 @@ public HttpHeaders basicAuthHeader(String username, String password){ set( AUTHORIZATION, authHeader ); }}; } + + /** + * This method is used to establish connection twc Rest API's by calling + * Teamwork cloud endpoints Time being added Admin account .Later need to + * implement secure methods like CyberArk + * + * @param twcRestUrl + * @return + */ + public ResponseEntity getRestResponse(String twcRestUrl, TeamworkCloud twc) { + RestTemplate restTemplate = getRestTemplate(); + HttpHeaders headers = basicAuthHeader(twc.getAdminUsername(), twc.getAdminPwd()); + ResponseEntity respEntity = null; + + try { + HttpEntity entityReq = new HttpEntity<>(null, headers); + respEntity = restTemplate.exchange(twcRestUrl, HttpMethod.GET, entityReq, String.class); + + } catch (Exception Ex) { + return null; + } + return respEntity; + } } diff --git a/twc/src/main/java/org/openmbee/mms/twc/utilities/TwcPermissionUtils.java b/twc/src/main/java/org/openmbee/mms/twc/utilities/TwcPermissionUtils.java index d3dba2678..2054cf19c 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/utilities/TwcPermissionUtils.java +++ b/twc/src/main/java/org/openmbee/mms/twc/utilities/TwcPermissionUtils.java @@ -156,4 +156,4 @@ private ResponseEntity getRestResponse(String twcRestUrl, TeamworkCloud return respEntity; } -} +} \ No newline at end of file diff --git a/twc/src/test/java/org/openmbee/mms/twc/security/TwcAuthenticationFilterTest.java b/twc/src/test/java/org/openmbee/mms/twc/security/TwcAuthenticationFilterTest.java index 20a727fef..66d3b3ef8 100644 --- a/twc/src/test/java/org/openmbee/mms/twc/security/TwcAuthenticationFilterTest.java +++ b/twc/src/test/java/org/openmbee/mms/twc/security/TwcAuthenticationFilterTest.java @@ -5,6 +5,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.openmbee.mms.twc.config.TwcConfig; +import org.openmbee.mms.users.security.DefaultUsersDetails; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.servlet.FilterChain; @@ -146,7 +147,7 @@ public void testPassedUserAuthentication() { FilterChain chain = mock(FilterChain.class); when(twcConfig.getAuthNProvider(anyString())).thenReturn(twcAuthProvider); when(twcAuthProvider.getAuthentication(anyString())).thenReturn("twcUser"); - when(userDetailsService.loadUserByUsername("twcUser")).thenReturn(mock(TwcUserDetails.class)); + when(userDetailsService.loadUserByUsername("twcUser")).thenReturn(mock(DefaultUsersDetails.class)); try { diff --git a/twc/twc.gradle b/twc/twc.gradle index 8b03a4615..10a41a6f6 100644 --- a/twc/twc.gradle +++ b/twc/twc.gradle @@ -1,5 +1,7 @@ dependencies { implementation project(':crud') + implementation project(':users') + implementation project(':groups') //TODO: Why is elastic search a dependency? diff --git a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java new file mode 100644 index 000000000..1d43c4251 --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java @@ -0,0 +1,113 @@ +package org.openmbee.mms.users.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.core.exceptions.UnauthorizedException; +import org.openmbee.mms.core.utils.AuthenticationUtils; +import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.users.security.UsersDetails; +import org.openmbee.mms.users.security.UsersDetailsService; +import org.openmbee.mms.users.objects.UserCreateRequest; +import org.openmbee.mms.users.objects.UsersResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Collection; + +@RestController +@Tag(name = "Auth") +public class UsersController { + + private UsersDetailsService usersDetailsService; + + @Autowired + public UsersController(UsersDetailsService usersDetailsService) { + this.usersDetailsService = usersDetailsService; + } + + @PostMapping(value = "/users", consumes = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) + public UsersResponse createOrUpdateUser(@RequestBody UserCreateRequest req) { + UsersResponse res = new UsersResponse(); + Collection users = new ArrayList<>(); + + UsersDetails userDetails; + try { + userDetails = usersDetailsService.loadUserByUsername(req.getUsername()); + } catch (UsernameNotFoundException e) { + users.add(usersDetailsService.register(req)); + res.setUsers(users); + return res; + } + users.add(usersDetailsService.update(req, userDetails.getUser())); + res.setUsers(users); + return res; + } + + @GetMapping(value = "/users") + @PreAuthorize("isAuthenticated()") + public UsersResponse getUsers() { + UsersResponse res = new UsersResponse(); + Collection users = usersDetailsService.getUsers(); + res.setUsers(users); + return res; + } + + @GetMapping(value = "/users/:username") + @PreAuthorize("isAuthenticated()") + public UsersResponse getUsers(@PathVariable String username) { + UsersResponse res = new UsersResponse(); + Collection users = new ArrayList<>(); + users.add(usersDetailsService.loadUserByUsername(username).getUser()); + res.setUsers(users); + return res; + } + + @GetMapping(value = "/whoami") + @PreAuthorize("isAuthenticated()") + public UsersResponse getCurrentUser() { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String user = authentication.getName(); + UsersResponse res = new UsersResponse(); + Collection users = new ArrayList<>(); + users.add(usersDetailsService.loadUserByUsername(user).getUser()); + res.setUsers(users); + return res; + } + + @PostMapping(value = "/password", consumes = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("isAuthenticated()") + public Object updatePassword(@RequestBody UserCreateRequest req, + Authentication auth) { + final String requester = auth.getName(); + final boolean requesterAdmin = AuthenticationUtils + .hasGroup(auth, AuthorizationConstants.MMSADMIN); + + try { + if (requesterAdmin || requester.equals(req.getUsername())) { + usersDetailsService.changeUserPassword(req.getUsername(), req.getPassword(), requesterAdmin); + } else { + throw new UnauthorizedException("Not authorized"); + } + + } catch (UsernameNotFoundException e) { + if (requesterAdmin) { + throw new NotFoundException("User not found"); + } else { + throw new UnauthorizedException("Not authorized"); + } + } + return ""; + } + +} \ No newline at end of file diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserCreateRequest.java b/users/src/main/java/org/openmbee/mms/users/objects/UserCreateRequest.java similarity index 67% rename from localuser/src/main/java/org/openmbee/mms/localuser/security/UserCreateRequest.java rename to users/src/main/java/org/openmbee/mms/users/objects/UserCreateRequest.java index 485096140..921f0b5e1 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserCreateRequest.java +++ b/users/src/main/java/org/openmbee/mms/users/objects/UserCreateRequest.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.localuser.security; +package org.openmbee.mms.users.objects; import java.io.Serializable; @@ -12,6 +12,8 @@ public class UserCreateRequest implements Serializable { private String firstname; private String lastname; private boolean admin; + private String type; + private Boolean enabled; public String getEmail() { return email; @@ -21,19 +23,19 @@ public void setEmail(String email) { this.email = email; } - public String getFirstname() { + public String getFirstName() { return firstname; } - public void setFirstname(String firstname) { + public void setFirstName(String firstname) { this.firstname = firstname; } - public String getLastname() { + public String getLastName() { return lastname; } - public void setLastname(String lastname) { + public void setLastName(String lastname) { this.lastname = lastname; } @@ -61,4 +63,15 @@ public void setAdmin(boolean admin) { this.admin = admin; } + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public Boolean isEnabled() { return enabled; } + + public void setEnabled(Boolean enabled) { this.enabled = enabled; } } diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/security/UsersResponse.java b/users/src/main/java/org/openmbee/mms/users/objects/UsersResponse.java similarity index 87% rename from localuser/src/main/java/org/openmbee/mms/localuser/security/UsersResponse.java rename to users/src/main/java/org/openmbee/mms/users/objects/UsersResponse.java index db931cb8d..7988a3a2a 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/security/UsersResponse.java +++ b/users/src/main/java/org/openmbee/mms/users/objects/UsersResponse.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.localuser.security; +package org.openmbee.mms.users.objects; import java.util.Collection; import org.openmbee.mms.json.UserJson; diff --git a/users/src/main/java/org/openmbee/mms/users/security/AbstractUsersDetailsService.java b/users/src/main/java/org/openmbee/mms/users/security/AbstractUsersDetailsService.java new file mode 100644 index 000000000..13d00f9f3 --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/security/AbstractUsersDetailsService.java @@ -0,0 +1,64 @@ +package org.openmbee.mms.users.security; + +import org.openmbee.mms.core.dao.GroupPersistence; +import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.core.dao.UserPersistence; +import org.openmbee.mms.json.UserJson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Optional; + +@Service +public abstract class AbstractUsersDetailsService implements UsersDetailsService { + + private UserPersistence userPersistence; + private UserGroupsPersistence userGroupsPersistence; + private GroupPersistence groupPersistence; + + + public GroupPersistence getGroupPersistence() { + return this.groupPersistence; + } + + public UserPersistence getUserPersistence() { + return this.userPersistence; + } + + public UserGroupsPersistence getUserGroupsPersistence() { + return this.userGroupsPersistence; + } + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; + } + + @Override + public UsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Optional user = getUserPersistence().findByUsername(username); + + if (user.isEmpty()) { + throw new UsernameNotFoundException( + String.format("No user found with username '%s'.", username)); + } + return new DefaultUsersDetails(user.get(), userGroupsPersistence.findGroupsAssignedToUser(username)); + } + + @Autowired + public void setUserPersistence(UserPersistence userPersistence) { + this.userPersistence = userPersistence; + } + + public UserJson saveUser(UserJson user) { + return getUserPersistence().save(user); + } + + public Collection getUsers() { + return getUserPersistence().findAll(); + } + + +} diff --git a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetails.java b/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetails.java similarity index 88% rename from twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetails.java rename to users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetails.java index 1076b7fe0..a4d0e399f 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetails.java +++ b/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetails.java @@ -1,21 +1,20 @@ -package org.openmbee.mms.twc.security; +package org.openmbee.mms.users.security; + +import java.util.ArrayList; +import java.util.Collection; import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.UserJson; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.ArrayList; -import java.util.Collection; -class TwcUserDetails implements UserDetails { +public class DefaultUsersDetails implements UsersDetails { private final UserJson user; private final Collection groups; - public TwcUserDetails(UserJson user, Collection groups) { + public DefaultUsersDetails(UserJson user, Collection groups) { this.user = user; this.groups = groups; } diff --git a/users/src/main/java/org/openmbee/mms/users/security/UsersDetails.java b/users/src/main/java/org/openmbee/mms/users/security/UsersDetails.java new file mode 100644 index 000000000..c53f11f57 --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/security/UsersDetails.java @@ -0,0 +1,8 @@ +package org.openmbee.mms.users.security; + +import org.openmbee.mms.json.UserJson; + +public interface UsersDetails extends org.springframework.security.core.userdetails.UserDetails { + + UserJson getUser(); +} diff --git a/users/src/main/java/org/openmbee/mms/users/security/UsersDetailsService.java b/users/src/main/java/org/openmbee/mms/users/security/UsersDetailsService.java new file mode 100644 index 000000000..b368ac5b5 --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/security/UsersDetailsService.java @@ -0,0 +1,22 @@ +package org.openmbee.mms.users.security; + +import org.openmbee.mms.json.UserJson; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.Collection; + +public interface UsersDetailsService extends org.springframework.security.core.userdetails.UserDetailsService { + + @Override + UsersDetails loadUserByUsername(String username) throws UsernameNotFoundException; + + UserJson register(T registerUserObject); + + UserJson saveUser(UserJson user); + + void changeUserPassword(String username, String password, boolean asAdmin); + + Collection getUsers(); + + UserJson update(T updateUserObject, UserJson user); +} diff --git a/localuser/localuser.gradle b/users/users.gradle similarity index 100% rename from localuser/localuser.gradle rename to users/users.gradle From 89815bb8637cffd64ae8030a0990253031396c71 Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 17 Apr 2024 09:36:47 -0600 Subject: [PATCH 04/19] Finalized refactor --- .../org/openmbee/mms/data/dao/GroupDAO.java | 17 ++++++++ .../dao/FederatedNodePersistence.java | 2 +- ...ltFederatedPermissionsDelegateFactory.java | 19 +++++++++ .../DefaultGroupPermissionsDelegate.java | 24 ++++++----- ...ratedPermissionUpdatesResponseBuilder.java | 2 +- .../DefaultPermissionsDelegateFactory.java | 42 ------------------- .../twc/security/TwcUserDetailsService.java | 31 +++++++++++++- .../mms/twc/utilities/AdminUtils.java | 5 --- 8 files changed, 82 insertions(+), 60 deletions(-) create mode 100644 data/src/main/java/org/openmbee/mms/data/dao/GroupDAO.java delete mode 100644 permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java diff --git a/data/src/main/java/org/openmbee/mms/data/dao/GroupDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/GroupDAO.java new file mode 100644 index 000000000..c8a74767b --- /dev/null +++ b/data/src/main/java/org/openmbee/mms/data/dao/GroupDAO.java @@ -0,0 +1,17 @@ +package org.openmbee.mms.data.dao; + +import org.openmbee.mms.data.domains.global.Group; + +import java.util.List; +import java.util.Optional; + +public interface GroupDAO { + + Optional findByGroupName(String id); + + Group save(Group group); + + void delete(Group group); + + List findAll(); +} \ No newline at end of file diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java index 446d8499c..44efd5f08 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java @@ -194,7 +194,7 @@ public void branchElements(RefJson parentBranch, CommitJson parentCommit, RefJso node.setDeleted(true); } } - nodeDAO.saveAll(info.getExistingNodeMap().values().stream().toList()); + nodeDAO.saveAll(info.getExistingNodeMap().values().stream().collect(Collectors.toList())); } else { for (Node n : nodeDAO.findAllByDeleted(false)) { diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java index 8a9cf14d6..4f4e01ea6 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java @@ -2,14 +2,17 @@ import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.data.dao.BranchGDAO; +import org.openmbee.mms.data.dao.GroupDAO; import org.openmbee.mms.data.dao.OrgDAO; import org.openmbee.mms.data.dao.ProjectDAO; import org.openmbee.mms.core.delegation.PermissionsDelegate; import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.data.domains.global.Branch; +import org.openmbee.mms.data.domains.global.Group; import org.openmbee.mms.data.domains.global.Organization; import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.json.RefJson; @@ -24,6 +27,7 @@ public class DefaultFederatedPermissionsDelegateFactory implements PermissionsDe private ProjectDAO projectDAO; private BranchGDAO branchDAO; private OrgDAO orgDAO; + private GroupDAO groupDAO; @Autowired public void setApplicationContext(ApplicationContext applicationContext) { @@ -45,6 +49,11 @@ public void setOrgDAO(OrgDAO orgDAO) { this.orgDAO = orgDAO; } + @Autowired + public void setGroupDAO(GroupDAO groupDAO) { + this.groupDAO = groupDAO; + } + @Override public PermissionsDelegate getPermissionsDelegate(ProjectJson project) { Optional projectOptional = projectDAO.findByProjectId(project.getProjectId()); @@ -74,4 +83,14 @@ public PermissionsDelegate getPermissionsDelegate(RefJson branch) { } return applicationContext.getBean(DefaultBranchPermissionsDelegate.class, branchOptional.get()); } + + @Override + public PermissionsDelegate getPermissionsDelegate(GroupJson group) { + ContextHolder.setContext(null); + Optional groupOptional = groupDAO.findByGroupName(group.getName()); + if(groupOptional.isEmpty()) { + throw new NotFoundException("group not found"); + } + return applicationContext.getBean(DefaultBranchPermissionsDelegate.class, groupOptional.get()); + } } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java index 3d58598c7..c80a49dc2 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java @@ -1,6 +1,5 @@ package org.openmbee.mms.federatedpersistence.permissions; -import org.openmbee.mms.core.builders.PermissionUpdateResponseBuilder; import org.openmbee.mms.core.builders.PermissionUpdatesResponseBuilder; import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.objects.PermissionResponse; @@ -24,8 +23,6 @@ import java.util.Optional; import java.util.Set; -import static org.openmbee.mms.core.config.AuthorizationConstants.EVERYONE; - public class DefaultGroupPermissionsDelegate extends AbstractDefaultPermissionsDelegate { private GroupUserPermRepository groupUserPermRepo; @@ -89,7 +86,7 @@ public void initializePermissions(String creator, boolean inherit) { throw new IllegalArgumentException("Cannot inherit permissions for a Group"); } - Optional user = getUserRepo().findByUsername(creator); + Optional user = getUserRepo().findByUsernameIgnoreCase(creator); Optional role = getRoleRepo().findByName(AuthorizationConstants.ADMIN); if (user.isEmpty()) { @@ -117,11 +114,17 @@ public void initializePermissions(String creator, boolean inherit) { @Override public boolean setInherit(boolean isInherit) { if(isInherit) { - throw new IllegalArgumentException("Cannot inherit permissions for a Group"); + throw new IllegalArgumentException("Cannot inherit permissions for an Org"); } return false; } + @Override + public PermissionResponse getInherit() { + //Orgs will not inherit + return PermissionResponse.getDefaultResponse(); + } + @Override public void setPublic(boolean isPublic) { group.setPublic(isPublic); @@ -130,12 +133,12 @@ public void setPublic(boolean isPublic) { @Override public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest req) { - PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); switch(req.getAction()) { case MODIFY: for (PermissionUpdateRequest.Permission p: req.getPermissions()) { - Optional user = getUserRepo().findByUsername(p.getName()); + Optional user = getUserRepo().findByUsernameIgnoreCase(p.getName()); Optional role = getRoleRepo().findByName(p.getRole()); if (user.isEmpty() || role.isEmpty()) { //throw exception or skip @@ -165,7 +168,7 @@ public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest re groupUserPermRepo.deleteByGroup(group); for (PermissionUpdateRequest.Permission p: req.getPermissions()) { - Optional user = getUserRepo().findByUsername(p.getName()); + Optional user = getUserRepo().findByUsernameIgnoreCase(p.getName()); Optional role = getRoleRepo().findByName(p.getRole()); if (!user.isPresent() || !role.isPresent()) { //throw exception or skip @@ -179,7 +182,7 @@ public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest re case REMOVE: Set users = new HashSet<>(); req.getPermissions().forEach(p -> { - Optional user = getUserRepo().findByUsername(p.getName()); + Optional user = getUserRepo().findByUsernameIgnoreCase(p.getName()); if(! user.isPresent()) { //throw or skip; return; @@ -196,7 +199,8 @@ public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest re @Override public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest req) { - PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); + switch(req.getAction()) { case MODIFY: for (PermissionUpdateRequest.Permission p: req.getPermissions()) { diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionUpdatesResponseBuilder.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionUpdatesResponseBuilder.java index 9922c4e77..7b766d081 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionUpdatesResponseBuilder.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionUpdatesResponseBuilder.java @@ -23,7 +23,7 @@ public FederatedPermissionUpdatesResponseBuilder insertGroups(PermissionUpdateRe } @Override - public PermissionUpdatesResponse getPermissionUpdatesReponse() { + public PermissionUpdatesResponse getPermissionUpdatesResponse() { PermissionUpdatesResponse permissionUpdatesResponse = new PermissionUpdatesResponse(); permissionUpdatesResponse.setInherit(this.inherit); permissionUpdatesResponse.setPublic(this.isPublic); diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java b/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java deleted file mode 100644 index ecae6eb24..000000000 --- a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.openmbee.mms.permissions.delegation; - -import org.openmbee.mms.core.delegation.PermissionsDelegate; -import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; -import org.openmbee.mms.data.domains.global.Branch; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.data.domains.global.Organization; -import org.openmbee.mms.data.domains.global.Project; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; - -public class DefaultPermissionsDelegateFactory implements PermissionsDelegateFactory { - - @Autowired - ApplicationContext applicationContext; - - @Override - public PermissionsDelegate getPermissionsDelegate(Project project) { - return autowire(new DefaultProjectPermissionsDelegate(project)); - } - - @Override - public PermissionsDelegate getPermissionsDelegate(Organization organization) { - return autowire(new DefaultOrgPermissionsDelegate(organization)); - } - - @Override - public PermissionsDelegate getPermissionsDelegate(Branch branch) { - return autowire(new DefaultBranchPermissionsDelegate(branch)); - } - - @Override - public PermissionsDelegate getPermissionsDelegate(Group group) { - return autowire(new DefaultGroupPermissionsDelegate(group)); - } - - private PermissionsDelegate autowire(PermissionsDelegate permissionsDelegate) { - applicationContext.getAutowireCapableBeanFactory().autowireBean(permissionsDelegate); - return permissionsDelegate; - } - -} diff --git a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java index 6712abc96..1029f3003 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java +++ b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java @@ -17,7 +17,7 @@ import java.util.*; @Service -public class TwcUserDetailsService extends AbstractUsersDetailsService { +public class TwcUserDetailsService extends AbstractUsersDetailsService { @Override public UsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { @@ -38,12 +38,41 @@ public UserJson addUser(String username) { //TODO: fill in user details from TWC user.setEnabled(true); + return register(user); + } + + @Override + public UserJson register(UserJson user) { return saveUser(user); } + @Override public void changeUserPassword(String username, String password, boolean asAdmin) { throw new TwcConfigurationException(HttpStatus.BAD_REQUEST, "Cannot Modify Password. Users for this server are controlled by Teamwork Cloud"); } + @Override + public UserJson update(UserJson userData, UserJson saveUser) { + if (saveUser.getEmail() == null || + !saveUser.getEmail().equals(userData.getEmail()) + ) { + saveUser.setEmail(userData.getEmail()); + } + if (saveUser.getFirstName() == null || + !saveUser.getFirstName().equals(userData.getFirstName()) + ) { + saveUser.setFirstName(userData.getFirstName()); + } + if (saveUser.getLastName() == null || + !saveUser.getLastName().equals(userData.getLastName()) + ) { + saveUser.setLastName(userData.getLastName()); + } + + return saveUser(saveUser); + } + + + } diff --git a/twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java b/twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java index c074edf77..f7e36ffd0 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java +++ b/twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java @@ -1,16 +1,11 @@ package org.openmbee.mms.twc.utilities; -import org.json.JSONArray; import org.json.JSONObject; import org.openmbee.mms.twc.TeamworkCloud; import org.openmbee.mms.twc.TeamworkCloudEndpoints; import org.springframework.beans.factory.annotation.Autowired; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.data.domains.global.User; import org.springframework.http.ResponseEntity; -import java.util.List; -import java.util.Optional; public class AdminUtils { private RestUtils restUtils; From 9ce517832f1b0fc1b71c2e08c90072cea1494144 Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 17 Apr 2024 09:48:01 -0600 Subject: [PATCH 05/19] removing GroupDAO --- .../org/openmbee/mms/data/dao/GroupDAO.java | 17 ----------------- ...aultFederatedPermissionsDelegateFactory.java | 10 +++++----- 2 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 data/src/main/java/org/openmbee/mms/data/dao/GroupDAO.java diff --git a/data/src/main/java/org/openmbee/mms/data/dao/GroupDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/GroupDAO.java deleted file mode 100644 index c8a74767b..000000000 --- a/data/src/main/java/org/openmbee/mms/data/dao/GroupDAO.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.openmbee.mms.data.dao; - -import org.openmbee.mms.data.domains.global.Group; - -import java.util.List; -import java.util.Optional; - -public interface GroupDAO { - - Optional findByGroupName(String id); - - Group save(Group group); - - void delete(Group group); - - List findAll(); -} \ No newline at end of file diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java index 4f4e01ea6..cb28dd4f6 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java @@ -2,7 +2,6 @@ import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.data.dao.BranchGDAO; -import org.openmbee.mms.data.dao.GroupDAO; import org.openmbee.mms.data.dao.OrgDAO; import org.openmbee.mms.data.dao.ProjectDAO; import org.openmbee.mms.core.delegation.PermissionsDelegate; @@ -16,6 +15,7 @@ import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.json.RefJson; +import org.openmbee.mms.rdb.repositories.GroupRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -27,7 +27,7 @@ public class DefaultFederatedPermissionsDelegateFactory implements PermissionsDe private ProjectDAO projectDAO; private BranchGDAO branchDAO; private OrgDAO orgDAO; - private GroupDAO groupDAO; + private GroupRepository groupRepository; @Autowired public void setApplicationContext(ApplicationContext applicationContext) { @@ -50,8 +50,8 @@ public void setOrgDAO(OrgDAO orgDAO) { } @Autowired - public void setGroupDAO(GroupDAO groupDAO) { - this.groupDAO = groupDAO; + public void setGroupRepository(GroupRepository groupRepository) { + this.groupRepository = groupRepository; } @Override @@ -87,7 +87,7 @@ public PermissionsDelegate getPermissionsDelegate(RefJson branch) { @Override public PermissionsDelegate getPermissionsDelegate(GroupJson group) { ContextHolder.setContext(null); - Optional groupOptional = groupDAO.findByGroupName(group.getName()); + Optional groupOptional = groupRepository.findByName(group.getName()); if(groupOptional.isEmpty()) { throw new NotFoundException("group not found"); } From ee80387bc91e39c81fb44da5511bc11980049836 Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 17 Apr 2024 10:48:27 -0600 Subject: [PATCH 06/19] reverting ldap changes --- .../mms/ldap/{config => }/LdapCondition.java | 2 +- .../ldap/{config => }/LdapSecurityConfig.java | 118 ++++++++++++------ .../security/LdapUsersDetailsService.java | 86 ------------- .../security/LocalUsersDetailsService.java | 1 - 4 files changed, 81 insertions(+), 126 deletions(-) rename ldap/src/main/java/org/openmbee/mms/ldap/{config => }/LdapCondition.java (93%) rename ldap/src/main/java/org/openmbee/mms/ldap/{config => }/LdapSecurityConfig.java (79%) delete mode 100644 ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java b/ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java similarity index 93% rename from ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java rename to ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java index 23b901670..9bb98618d 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.ldap.config; +package org.openmbee.mms.ldap; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java b/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java similarity index 79% rename from ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java rename to ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java index d239d87d8..36de07b2d 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java @@ -1,8 +1,9 @@ -package org.openmbee.mms.ldap.config; +package org.openmbee.mms.ldap; import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.dao.GroupPersistence; import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.core.dao.UserPersistence; import org.openmbee.mms.core.exceptions.ForbiddenException; import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.UserJson; @@ -48,31 +49,6 @@ public class LdapSecurityConfig { private static Logger logger = LoggerFactory.getLogger(LdapSecurityConfig.class); - private LdapUsersDetailsService userDetailsService; - - private GroupPersistence groupPersistence; - - private UserGroupsPersistence userGroupsPersistence; - - @Autowired - public void setGroupPersistence(GroupPersistence groupPersistence) { - this.groupPersistence = groupPersistence; - } - - @Autowired - public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { - this.userGroupsPersistence = userGroupsPersistence; - } - - - @Autowired - public void setUserDetailsService(LdapUsersDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; - } - - - - @Value("${ldap.ad.enabled:false}") private Boolean adEnabled; @@ -94,6 +70,21 @@ public void setUserDetailsService(LdapUsersDetailsService userDetailsService) { @Value("#{'${ldap.user.dn.pattern:uid={0}}'.split(';')}") private List userDnPattern; + @Value("${ldap.user.attributes.username:uid}") + private String userAttributesUsername; + + @Value("${ldap.user.attributes.firstname:givenname}") + private String userAttributesFirstName; + + @Value("${ldap.user.attributes.lastname:sn}") + private String userAttributesLastName; + + @Value("${ldap.user.attributes.email:mail}") + private String userAttributesEmail; + + @Value("${ldap.user.attributes.update:24}") + private int userAttributesUpdate; + @Value("${ldap.group.search.base:#{''}}") private String groupSearchBase; @@ -108,6 +99,24 @@ public void setUserDetailsService(LdapUsersDetailsService userDetailsService) { @Value("${ldap.user.search.filter:(uid={0})}") private String userSearchFilter; + private UserPersistence userPersistence; + private GroupPersistence groupPersistence; + private UserGroupsPersistence userGroupsPersistence; + + @Autowired + public void setUserPersistence(UserPersistence userPersistence) { + this.userPersistence = userPersistence; + } + + @Autowired + public void setGroupPersistence(GroupPersistence groupPersistence) { + this.groupPersistence = groupPersistence; + } + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; + } @Autowired public void configureLdapAuth(AuthenticationManagerBuilder auth, @@ -157,16 +166,20 @@ private CustomLdapAuthoritiesPopulator(BaseLdapPathContextSource ldapContextSour public Collection getGrantedAuthorities( DirContextOperations userData, String username) { logger.debug("Populating authorities using LDAP"); - UserJson user = new UserJson(); - try { - UsersDetails userDetails = userDetailsService.loadUserByUsername(username); - user = userDetails.getUser(); - } catch (UsernameNotFoundException e) { + Optional userOptional = userPersistence.findByUsername(username); + + if (userOptional.isEmpty()) { logger.info("No user record for {} in the userRepository, creating...", userData.getDn()); - user = userDetailsService.register(userData); + UserJson newUser = createLdapUser(userData); + userOptional = Optional.of(newUser); } - user = userDetailsService.update(userData, user); + UserJson user = userOptional.get(); + if (user.getModified() != null && Instant.parse(user.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { + saveLdapUser(userData, user); + } + user.setPassword(null); + StringBuilder userDnBuilder = new StringBuilder(); userDnBuilder.append(userData.getDn().toString()); if (providerBase != null && !providerBase.isEmpty()) { @@ -187,18 +200,21 @@ public Collection getGrantedAuthorities( andFilter.and(groupsFilter); andFilter.and(orFilter); - String filter = andFilter.encode(); - logger.debug("Searching LDAP with filter: {}", filter); + String filter = andFilter.encode(); + logger.debug("Searching LDAP with filter: {}", filter); + Set memberGroups = ldapTemplate .searchForSingleAttributeValues(groupSearchBase, filter, new Object[]{""}, groupRoleAttribute); logger.debug("LDAP search result: {}", Arrays.toString(memberGroups.toArray())); + + userPersistence.save(user); //Add groups to user Set addGroups = new HashSet<>(); for (String memberGroup : memberGroups) { Optional group = groupPersistence.findByName(memberGroup); - group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), username)); + group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), user.getUsername())); group.ifPresent(addGroups::add); } @@ -261,7 +277,33 @@ public LdapContextSource contextSource() { return contextSource; } - + private UserJson saveLdapUser(DirContextOperations userData, UserJson saveUser) { + if (saveUser.getEmail() == null || + !saveUser.getEmail().equals(userData.getStringAttribute(userAttributesEmail)) + ) { + saveUser.setEmail(userData.getStringAttribute(userAttributesEmail)); + } + if (saveUser.getFirstName() == null || + !saveUser.getFirstName().equals(userData.getStringAttribute(userAttributesFirstName)) + ) { + saveUser.setFirstName(userData.getStringAttribute(userAttributesFirstName)); + } + if (saveUser.getLastName() == null || + !saveUser.getLastName().equals(userData.getStringAttribute(userAttributesLastName)) + ) { + saveUser.setLastName(userData.getStringAttribute(userAttributesLastName)); + } - + return saveUser; + } + + private UserJson createLdapUser(DirContextOperations userData) { + String username = userData.getStringAttribute(userAttributesUsername); + logger.debug("Creating user for {} using LDAP", username); + UserJson user = saveLdapUser(userData, new UserJson()); + user.setUsername(username); + user.setEnabled(true); + user.setAdmin(false); + return userPersistence.save(user); + } } \ No newline at end of file diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java deleted file mode 100644 index 57afe8ae6..000000000 --- a/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.openmbee.mms.ldap.security; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; - -import org.openmbee.mms.core.exceptions.ForbiddenException; -import org.openmbee.mms.json.UserJson; -import org.openmbee.mms.users.security.AbstractUsersDetailsService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.ldap.core.DirContextOperations; -import org.springframework.stereotype.Service; - -@Service -public class LdapUsersDetailsService extends AbstractUsersDetailsService { - - private static Logger logger = LoggerFactory.getLogger(LdapUsersDetailsService.class); - - @Value("${ldap.provider.base:#{null}}") - private String providerBase; - - @Value("${ldap.user.search.filter:(uid={0})}") - private String userSearchFilter; - - @Value("${ldap.user.attributes.username:uid}") - private String userAttributesUsername; - - @Value("${ldap.user.attributes.firstname:givenname}") - private String userAttributesFirstName; - - @Value("${ldap.user.attributes.lastname:sn}") - private String userAttributesLastName; - - @Value("${ldap.user.attributes.email:mail}") - private String userAttributesEmail; - - @Value("${ldap.user.attributes.update:24}") - private int userAttributesUpdate; - - @Value("${ldap.group.role.attribute:cn}") - private String groupRoleAttribute; - - @Value("${ldap.group.search.filter:(uniqueMember={0})}") - private String groupSearchFilter; - - public UserJson register(DirContextOperations userData) { - String username = userData.getStringAttribute(userAttributesUsername); - logger.debug("Creating user for {} using LDAP", username); - UserJson user = update(userData, new UserJson()); - user.setUsername(username); - user.setEnabled(true); - user.setAdmin(false); - return saveUser(user); - } - - public void changeUserPassword(String username, String password, boolean asAdmin) { - throw new ForbiddenException("Cannot change or set passwords for external users."); - } - - public UserJson update(DirContextOperations userData, UserJson saveUser) { - if (saveUser.getModified() != null && Instant.parse(saveUser.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { - if (saveUser.getEmail() == null || - !saveUser.getEmail().equals(userData.getStringAttribute(userAttributesEmail)) - ) { - saveUser.setEmail(userData.getStringAttribute(userAttributesEmail)); - } - if (saveUser.getFirstName() == null || - !saveUser.getFirstName().equals(userData.getStringAttribute(userAttributesFirstName)) - ) { - saveUser.setFirstName(userData.getStringAttribute(userAttributesFirstName)); - } - if (saveUser.getLastName() == null || - !saveUser.getLastName().equals(userData.getStringAttribute(userAttributesLastName)) - ) { - saveUser.setLastName(userData.getStringAttribute(userAttributesLastName)); - } - - } - - saveUser.setPassword(null); - - return saveUser(saveUser); - } - -} diff --git a/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java b/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java index 3c560da2c..aba52e988 100644 --- a/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java +++ b/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java @@ -12,7 +12,6 @@ import org.openmbee.mms.users.security.AbstractUsersDetailsService; import org.openmbee.mms.users.security.DefaultUsersDetails; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; From bec163608dac25fa6309cc3a369071b11274f7b1 Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 17 Apr 2024 10:50:03 -0600 Subject: [PATCH 07/19] fix lingering import --- .../main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java b/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java index 36de07b2d..cbffaf1eb 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java @@ -4,13 +4,8 @@ import org.openmbee.mms.core.dao.GroupPersistence; import org.openmbee.mms.core.dao.UserGroupsPersistence; import org.openmbee.mms.core.dao.UserPersistence; -import org.openmbee.mms.core.exceptions.ForbiddenException; import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.UserJson; -import org.openmbee.mms.ldap.security.LdapUsersDetailsService; -import org.openmbee.mms.users.security.AbstractUsersDetailsService; -import org.openmbee.mms.users.security.UsersDetails; -import org.openmbee.mms.users.security.UsersDetailsService; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -34,7 +29,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.SpringSecurityLdapTemplate; import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; From 2c51e171781504d84a4cfb0f0929835892578e7e Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 17 Apr 2024 14:15:04 -0600 Subject: [PATCH 08/19] fix commit retrieval loop --- .../mms/rdb/repositories/commit/CommitDAOImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java index dc15fa426..df581f316 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java @@ -13,6 +13,7 @@ import org.openmbee.mms.data.dao.BranchDAO; import org.openmbee.mms.data.dao.CommitDAO; import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.config.Constants; import org.openmbee.mms.data.domains.scoped.Branch; import org.openmbee.mms.data.domains.scoped.Commit; import org.openmbee.mms.rdb.repositories.BaseDAOImpl; @@ -63,7 +64,7 @@ public Optional findById(long id) { String sql = "SELECT * FROM commits WHERE id = ?"; List l = getConn() - .query(sql, new Object[]{id}, new CommitRowMapper()); + .query(sql, new CommitRowMapper(), new Object[]{id}); return l.isEmpty() ? Optional.empty() : Optional.of(l.get(0)); } @@ -72,7 +73,7 @@ public Optional findByCommitId(String commitId) { String sql = "SELECT * FROM commits WHERE commitid = ?"; List l = getConn() - .query(sql, new Object[]{commitId}, new CommitRowMapper()); + .query(sql, new CommitRowMapper(), new Object[]{commitId}); return l.isEmpty() ? Optional.empty() : Optional.of(l.get(0)); } @@ -156,7 +157,7 @@ public List findByRefAndTimestampAndLimit(Branch ref, Instant timestamp, currentRef = ref.getParentRefId(); currentCid = ref.getParentCommit(); - if (currentRef == null) { + if (currentRef == null || currentRef == Constants.MASTER_BRANCH) { break; } Optional parent = branchRepository.findByBranchId(currentRef); From 4c5de3647039e3e3afc40e7324cb23fb24194175 Mon Sep 17 00:00:00 2001 From: Enquier Date: Thu, 18 Apr 2024 16:07:25 -0600 Subject: [PATCH 09/19] add missing set to delete --- .../mms/federatedpersistence/dao/FederatedBranchPersistence.java | 1 + 1 file changed, 1 insertion(+) diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java index bfc586066..fa255f525 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java @@ -60,6 +60,7 @@ public RefJson save(RefJson refJson) { scopedBranch.setTimestamp(Formats.FORMATTER.parse(refJson.getCreated(), Instant::from)); scopedBranch.setParentRefId(refJson.getParentRefId()); scopedBranch.setDocId(refJson.getDocId()); + scopedBranch.setDeleted(Boolean.parseBoolean(refJson.getIsDeleted())); //Setup global Branch object Optional project = projectDAO.findByProjectId(refJson.getProjectId()); From 28fbf1dd158afcf85bd4e56a74d29136fd6235ae Mon Sep 17 00:00:00 2001 From: Enquier Date: Thu, 18 Apr 2024 16:14:27 -0600 Subject: [PATCH 10/19] stronger comparison --- .../org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java index df581f316..9a82b3f07 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java @@ -157,7 +157,7 @@ public List findByRefAndTimestampAndLimit(Branch ref, Instant timestamp, currentRef = ref.getParentRefId(); currentCid = ref.getParentCommit(); - if (currentRef == null || currentRef == Constants.MASTER_BRANCH) { + if (currentRef == null || currentRef.equals(Constants.MASTER_BRANCH)) { break; } Optional parent = branchRepository.findByBranchId(currentRef); From 2f01a6cd6544375b4d98e4603a24bee020054e5a Mon Sep 17 00:00:00 2001 From: Enquier Date: Fri, 3 May 2024 13:22:36 -0600 Subject: [PATCH 11/19] more work on persistance refactor and auth updates --- .../openmbee/mms/core/dao/OrgPersistence.java | 4 +- .../mms/core/dao/ProjectPersistence.java | 4 +- .../core/objects/OrganizationsResponse.java | 3 +- .../branches/BranchesController.java | 20 +- .../crud/controllers/orgs/OrgsController.java | 38 +- .../projects/ProjectsController.java | 7 +- .../crud/domain/DefaultNodeUpdateFilter.java | 4 +- .../crud/services/DefaultProjectService.java | 8 +- .../mms/crud/services/OrgDeleteService.java | 83 +++++ .../crud/services/ProjectDeleteService.java | 8 +- .../mms/data/domains/global/Organization.java | 11 + .../mms/data/domains/global/Project.java | 1 + .../mms/data/domains/global/User.java | 10 + .../mms/elastic/ProjectElasticImpl.java | 1 + .../dao/FederatedOrgPersistence.java | 27 +- .../dao/FederatedProjectPersistence.java | 29 +- .../domain/FederatedNodeChangeDomain.java | 2 +- .../java/org/openmbee/mms/json/BaseJson.java | 14 +- .../java/org/openmbee/mms/json/OrgJson.java | 6 + .../org/openmbee/mms/json/ProjectJson.java | 6 + .../java/org/openmbee/mms/json/UserJson.java | 24 +- ldap/ldap.gradle | 1 + .../mms/ldap/{ => config}/LdapCondition.java | 2 +- .../ldap/{ => config}/LdapSecurityConfig.java | 111 +++--- .../security/LdapUsersDetailsService.java | 348 ++++++++++++++++++ .../localauth/config/AuthProviderConfig.java | 6 +- .../localauth/config/LocalAuthCondition.java | 15 + .../security/LocalUsersDetailsService.java | 104 +----- .../twc/security/TwcUserDetailsService.java | 10 +- .../mms/users/controller/UsersController.java | 35 +- .../security/AbstractUsersDetailsService.java | 64 ---- .../DefaultPasswordEncoderConfig.java | 9 + .../security/DefaultUsersDetailsService.java | 128 +++++++ .../security}/UserPasswordRulesConfig.java | 2 +- 34 files changed, 849 insertions(+), 296 deletions(-) create mode 100644 crud/src/main/java/org/openmbee/mms/crud/services/OrgDeleteService.java rename ldap/src/main/java/org/openmbee/mms/ldap/{ => config}/LdapCondition.java (93%) rename ldap/src/main/java/org/openmbee/mms/ldap/{ => config}/LdapSecurityConfig.java (77%) create mode 100644 ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java create mode 100644 localauth/src/main/java/org/openmbee/mms/localauth/config/LocalAuthCondition.java delete mode 100644 users/src/main/java/org/openmbee/mms/users/security/AbstractUsersDetailsService.java create mode 100644 users/src/main/java/org/openmbee/mms/users/security/DefaultPasswordEncoderConfig.java create mode 100644 users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java rename {localauth/src/main/java/org/openmbee/mms/localauth/config => users/src/main/java/org/openmbee/mms/users/security}/UserPasswordRulesConfig.java (92%) diff --git a/core/src/main/java/org/openmbee/mms/core/dao/OrgPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/OrgPersistence.java index c9dd84799..41c8c78dd 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/OrgPersistence.java +++ b/core/src/main/java/org/openmbee/mms/core/dao/OrgPersistence.java @@ -13,7 +13,9 @@ public interface OrgPersistence { Collection findAll(); - OrgJson deleteById(String orgId); + void deleteById(String orgId); + + void archiveById(String orgId); boolean hasPublicPermissions(String orgId); } diff --git a/core/src/main/java/org/openmbee/mms/core/dao/ProjectPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/ProjectPersistence.java index 3bf83cc6e..c83ab7477 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/ProjectPersistence.java +++ b/core/src/main/java/org/openmbee/mms/core/dao/ProjectPersistence.java @@ -21,9 +21,9 @@ public interface ProjectPersistence { Collection findAllByOrgId(String orgId); - void softDelete(String projectId); + void archiveById(String projectId); - void hardDelete(String projectId); + void deleteById(String projectId); boolean inheritsPermissions(String projectId); diff --git a/core/src/main/java/org/openmbee/mms/core/objects/OrganizationsResponse.java b/core/src/main/java/org/openmbee/mms/core/objects/OrganizationsResponse.java index eda81acde..d318b83de 100644 --- a/core/src/main/java/org/openmbee/mms/core/objects/OrganizationsResponse.java +++ b/core/src/main/java/org/openmbee/mms/core/objects/OrganizationsResponse.java @@ -17,7 +17,8 @@ public List getOrgs() { return orgs; } - public void setOrgs(List orgs) { + public OrganizationsResponse setOrgs(List orgs) { this.orgs = orgs; + return this; } } diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java index d69dcf0df..9fa281d38 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java @@ -4,6 +4,8 @@ import java.util.Optional; import java.util.UUID; + +import org.openmbee.mms.core.config.Constants; import org.openmbee.mms.core.config.Privileges; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.MMSException; @@ -40,17 +42,19 @@ public BranchesController(BranchService branchService) { @PreAuthorize("@mss.hasProjectPrivilege(authentication, #projectId, 'PROJECT_READ', true)") public RefsResponse getAllRefs( @PathVariable String projectId, + @RequestParam(required = false, defaultValue = Constants.FALSE) boolean includeDeleted, Authentication auth) { getProjectType(projectId); - RefsResponse res = branchService.getBranches(projectId); + List filtered = new ArrayList<>(); if (!permissionService.isProjectPublic(projectId)) { - List filtered = new ArrayList<>(); for (RefJson ref: res.getRefs()) { try { if (mss.hasBranchPrivilege(auth, projectId, ref.getId(), - Privileges.BRANCH_READ.name(), false)) { + Privileges.BRANCH_READ.name(), false) + && (!ref.isDeleted() || includeDeleted)) + { filtered.add(ref); } } catch (MMSException e) { @@ -58,8 +62,16 @@ public RefsResponse getAllRefs( projectId + ", refId=" + ref.getId(), e); } } - res.setRefs(filtered); + } else if (!includeDeleted) { + for (RefJson ref: res.getRefs()) { + if (!ref.isDeleted()) { + filtered.add(ref); + } + } + } else { + filtered = res.getRefs(); } + res.setRefs(filtered); return res; } diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java index 05b408a6c..816245ad6 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java @@ -6,8 +6,10 @@ import java.util.Collection; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import java.util.Optional; +import org.openmbee.mms.core.config.Constants; import org.openmbee.mms.core.config.Formats; import org.openmbee.mms.core.config.Privileges; import org.openmbee.mms.core.dao.OrgPersistence; @@ -16,9 +18,11 @@ import org.openmbee.mms.core.objects.Rejection; import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.controllers.BaseController; +import org.openmbee.mms.crud.services.OrgDeleteService; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; @@ -29,6 +33,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -38,18 +43,26 @@ public class OrgsController extends BaseController { OrgPersistence organizationRepository; + OrgDeleteService orgDeleteService; + @Autowired public OrgsController(OrgPersistence organizationRepository) { this.organizationRepository = organizationRepository; } + @Autowired + public void setOrgDeleteService(OrgDeleteService orgDeleteService) { + this.orgDeleteService = orgDeleteService; + } + @GetMapping - public OrganizationsResponse getAllOrgs( Authentication auth) { + public OrganizationsResponse getAllOrgs(@RequestParam(required = false, defaultValue = Constants.FALSE) boolean includeArchived, Authentication auth) { OrganizationsResponse response = new OrganizationsResponse(); Collection allOrgs = organizationRepository.findAll(); for (OrgJson org : allOrgs) { - if (mss.hasOrgPrivilege(auth, org.getId(), Privileges.ORG_READ.name(), true)) { + if (mss.hasOrgPrivilege(auth, org.getId(), Privileges.ORG_READ.name(), true) + && (!Constants.TRUE.equals(org.getIsArchived()) || includeArchived)) { response.getOrgs().add(org); } } @@ -106,6 +119,14 @@ public OrganizationsResponse createOrUpdateOrgs( org.setCreator(auth.getName()); } } + if (org.getIsArchived() != null && !org.getIsArchived().equals(o.getIsArchived())) { + List orgProjs = projectPersistence.findAllByOrgId(org.getId()).stream().collect(Collectors.toList()); + //Un/Archive all projects contained by org + for (ProjectJson proj: orgProjs) { + proj.setIsArchived(org.getIsArchived()); + projectPersistence.update(proj); + } + } logger.info("Saving organization: {}", org.getId()); OrgJson saved = organizationRepository.save(org); if (newOrg) { @@ -121,18 +142,19 @@ public OrganizationsResponse createOrUpdateOrgs( @DeleteMapping(value = "/{orgId}") @PreAuthorize("@mss.hasOrgPrivilege(authentication, #orgId, 'ORG_DELETE', false)") - public OrganizationsResponse deleteOrg(@PathVariable String orgId) { + public OrganizationsResponse deleteOrg( + @PathVariable String orgId, + @RequestParam(required = false, defaultValue = Constants.FALSE) boolean hard) { OrganizationsResponse response = new OrganizationsResponse(); Optional orgOption = organizationRepository.findById(orgId); if (!orgOption.isPresent()) { throw new NotFoundException(response.addMessage("Organization not found.")); } - if (!projectPersistence.findAllByOrgId(orgId).isEmpty()) { - throw new BadRequestException(response.addMessage("Organization is not empty")); + if (!projectPersistence.findAllByOrgId(orgId).isEmpty() && hard) { + throw new BadRequestException(response.addMessage("Cannot Hard Delete Organization that contains Projects")); } - OrgJson deleted = organizationRepository.deleteById(orgId); - response.setOrgs(List.of(deleted)); - return response; + + return orgDeleteService.deleteOrg(orgId, hard); } } diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java index 7e9a2ab92..dd8f3f76e 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java @@ -45,7 +45,7 @@ public void setProjectSchemas(ProjectSchemas projectSchemas) { } @GetMapping - public ProjectsResponse getAllProjects(Authentication auth, @RequestParam(required = false) String orgId) { + public ProjectsResponse getAllProjects(Authentication auth, @RequestParam(required = false) String orgId, @RequestParam(required = false, defaultValue = Constants.FALSE) boolean includeArchived) { ProjectsResponse response = new ProjectsResponse(); Collection allProjects = @@ -54,7 +54,7 @@ public ProjectsResponse getAllProjects(Authentication auth, @RequestParam(requir try { if (mss.hasProjectPrivilege(auth, projectJson.getProjectId(), Privileges.PROJECT_READ.name(), true) && projectJson.getDocId() != null - && !Constants.TRUE.equals(projectJson.getIsDeleted())) { + && (!Constants.TRUE.equals(projectJson.getIsArchived()) || includeArchived)) { response.getProjects().add(projectJson); } } catch(NotFoundException ex) { @@ -76,9 +76,6 @@ public ProjectsResponse getProject( throw new NotFoundException(response.addMessage("Project not found")); } response.getProjects().add(projectOption.get()); - if (Constants.TRUE.equals(projectOption.get().getIsDeleted())) { - throw new DeletedException(response); - } return response; } diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java b/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java index 7174db827..9d266c99b 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java @@ -22,7 +22,7 @@ public class DefaultNodeUpdateFilter implements NodeUpdateFilter { @Override public boolean filterUpdate(NodeChangeInfo info, ElementJson updated, ElementJson existing) { if (!info.getOverwrite()) { - if (Constants.TRUE.equals(existing.getIsDeleted()) || isUpdated(updated, existing, info)) { + if (Constants.TRUE.equals(existing.getIsArchived()) || isUpdated(updated, existing, info)) { return diffUpdateJson(updated, existing, info); } else { return false; @@ -61,7 +61,7 @@ protected boolean diffUpdateJson(BaseJson element, Map existi } } element.merge(existing); - element.remove(ElementJson.IS_DELETED); + element.remove(ElementJson.IS_ARCHIVED); return true; } diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java index bfbca9148..82696c3fb 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java @@ -63,18 +63,18 @@ public ProjectJson create(ProjectJson project) { try { //TODO Transaction start ProjectJson savedProjectJson = projectPersistence.save(project); - + savedProjectJson.setIsArchived("false"); //create and save master branch. We're combining operations with the branch unifiedDAO. branchPersistence.save(createMasterRefJson(savedProjectJson)); //TODO Transaction commit - + eventPublisher.forEach(pub -> pub.publish( EventObject.create(savedProjectJson.getId(), Constants.MASTER_BRANCH, "project_created", savedProjectJson))); return savedProjectJson; } catch (Exception e) { logger.error("Couldn't create project: {}", project.getProjectId(), e); //Need to clean up in case of partial creation - projectPersistence.hardDelete(project.getProjectId()); + projectPersistence.deleteById(project.getProjectId()); //TODO Transaction rollback (could include project delete in rollback) } throw new InternalErrorException("Could not create project"); @@ -112,7 +112,7 @@ public RefJson createMasterRefJson(ProjectJson project) { branchJson.setCreated(project.getCreated()); branchJson.setProjectId(project.getId()); branchJson.setCreator(project.getCreator()); - branchJson.setDeleted(false); + branchJson.setIsArchived("false"); return branchJson; } } diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/OrgDeleteService.java b/crud/src/main/java/org/openmbee/mms/crud/services/OrgDeleteService.java new file mode 100644 index 000000000..026032581 --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/services/OrgDeleteService.java @@ -0,0 +1,83 @@ +package org.openmbee.mms.crud.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.dao.OrgPersistence; +import org.openmbee.mms.core.dao.ProjectPersistence; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.core.objects.OrganizationsResponse; +import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Service +public class OrgDeleteService { + private OrgPersistence orgPersistence; + private ProjectPersistence projectPersistence; + private ProjectDeleteService projectDeleteService; + protected ObjectMapper om; + + @Autowired + public void setOrgPersistence(OrgPersistence orgPersistence) { + this.orgPersistence = orgPersistence; + } + + @Autowired + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; + } + + @Autowired + public void setProjectDeleteService(ProjectDeleteService projectDeleteService) { + this.projectDeleteService = projectDeleteService; + } + + @Autowired + public void setOm(ObjectMapper om) { + this.om = om; + } + + public OrganizationsResponse deleteOrg(String orgId, boolean hard) { + OrganizationsResponse response = new OrganizationsResponse(); + OrgJson orgJson; + Optional orgJsonOption = orgPersistence.findById(orgId); + + List res = new ArrayList<>(); + + //Do not try to do a soft delete when an error condition is present. + if(orgJsonOption.isEmpty() && !hard) { + throw new NotFoundException("Project state is invalid"); + } + + orgJson = orgJsonOption.orElseGet(() -> { + OrgJson newOrg = new OrgJson(); + newOrg.setId(orgId); + return newOrg; + }); + + if(hard){ + orgPersistence.deleteById(orgId); + orgJson.setDeleted(true); + } else { + List orgProjs = projectPersistence.findAllByOrgId(orgId).stream().collect(Collectors.toList()); + //Archive all projects contained by org + for (ProjectJson proj: orgProjs) { + projectDeleteService.deleteProject(proj.getId(), false); + } + orgPersistence.archiveById(orgId); + orgJson.setIsArchived(Constants.TRUE); + } + + + res.add(orgJson); + return response.setOrgs(res); + } +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java b/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java index 4736e3cf2..b8488eb43 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java @@ -50,12 +50,14 @@ public ProjectsResponse deleteProject(String projectId, boolean hard) { }); if(hard){ - projectPersistence.hardDelete(projectId); + projectPersistence.deleteById(projectId); + projectJson.setDeleted(true); } else { - projectPersistence.softDelete(projectId); + projectPersistence.archiveById(projectId); + projectJson.setIsArchived(Constants.TRUE); } - projectJson.setIsDeleted(Constants.TRUE); + res.add(projectJson); return response.setProjects(res); } diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/Organization.java b/data/src/main/java/org/openmbee/mms/data/domains/global/Organization.java index 25d572da5..0b96c1d6d 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/Organization.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/Organization.java @@ -36,7 +36,10 @@ public class Organization extends Base { @JsonProperty("public") private boolean isPublic; + private boolean deleted; + public Organization() { + this.deleted = false; } public String getOrganizationName() { @@ -86,4 +89,12 @@ public boolean isPublic() { public void setPublic(boolean aPublic) { isPublic = aPublic; } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } } diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/Project.java b/data/src/main/java/org/openmbee/mms/data/domains/global/Project.java index 70ce8d78f..00831db05 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/Project.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/Project.java @@ -61,6 +61,7 @@ public Project() { public Project(String projectId, String projectName) { this.projectId = projectId; this.projectName = projectName; + this.deleted = false; } public String getProjectName() { diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/User.java b/data/src/main/java/org/openmbee/mms/data/domains/global/User.java index bc2ccdb27..9fd90fbd8 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/User.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/User.java @@ -42,6 +42,8 @@ public class User extends Base { @JoinTable(name = "users_groups", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "group_id", referencedColumnName = "id"), uniqueConstraints=@UniqueConstraint(columnNames={"user_id","group_id"})) private Set groups; + private String type; + public User() { this.groups = new HashSet<>(); } @@ -152,4 +154,12 @@ public void setAdmin(boolean admin) { this.admin = admin; } + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + } diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java b/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java index 1d4a1d18f..29751bc26 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java @@ -38,6 +38,7 @@ public void create(String projectId) { ProjectJson projectJson = newInstance(); projectJson.setProjectId(projectId); projectJson.setType("default"); + projectJson.setIsArchived("false"); create(projectJson); } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java index c293e6804..65de4a4de 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java @@ -2,11 +2,15 @@ import org.openmbee.mms.core.exceptions.ForbiddenException; import org.openmbee.mms.data.dao.OrgDAO; +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.core.dao.OrgPersistence; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.data.domains.global.Organization; +import org.openmbee.mms.data.domains.global.Project; import org.openmbee.mms.federatedpersistence.utils.FederatedJsonUtils; import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -36,6 +40,9 @@ public OrgJson save(OrgJson orgJson) { Organization organization = organizationOptional.orElse(new Organization()); organization.setOrganizationId(orgJson.getId()); organization.setOrganizationName(orgJson.getName()); + if (orgJson.getIsArchived() != null) { + organization.setDeleted(Boolean.parseBoolean(orgJson.getIsArchived())); + } return getOrgJson(orgDAO.save(organization)); } @@ -50,13 +57,27 @@ public Collection findAll() { } @Override - public OrgJson deleteById(String orgId) { + public void deleteById(String orgId) { Optional organization = orgDAO.findByOrganizationId(orgId); if(organization.isEmpty()) { throw new NotFoundException(getOrgNotFoundMessage(orgId)); } orgDAO.delete(organization.get()); - return getOrgJson(organization.get()); + } + + @Override + public void archiveById(String orgId) { + //TODO not called locally, otherwise delete + ContextHolder.setContext(orgId); + Optional org = orgDAO.findByOrganizationId(orgId); + + if (org.isEmpty()) { + throw new NotFoundException("Org state is invalid, cannot delete."); + } + + Organization p = org.get(); + p.setDeleted(true); + orgDAO.save(p); } @Override @@ -71,6 +92,8 @@ public boolean hasPublicPermissions(String orgId) { protected OrgJson getOrgJson(Organization organization) { OrgJson orgJson = new OrgJson(); orgJson.merge(jsonUtils.convertToMap(organization)); + orgJson.setIsArchived(String.valueOf(organization.isDeleted())); + orgJson.remove(OrgJson.DELETED); return orgJson; } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java index 68d545a13..927c6b3a3 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java @@ -64,7 +64,9 @@ public List findAllById(Set projectIds) { if (projectOption.isPresent()) { Project project = projectOption.get(); ContextHolder.setContext(project.getProjectId()); - return projectIndexDAO.findById(project.getDocId()).orElse(null); + ProjectJson projectJson = projectIndexDAO.findById(project.getDocId()).orElse(null); + projectJson.setIsArchived(String.valueOf(project.isDeleted())); + return projectJson; } return null; }).filter(Objects::nonNull).collect(Collectors.toList()); @@ -73,8 +75,11 @@ public List findAllById(Set projectIds) { @Override public List findAll() { return projectDAO.findAll().stream().map(project -> { + ContextHolder.setContext(project.getProjectId()); - return projectIndexDAO.findById(project.getDocId()).orElse(null); + ProjectJson projectJson = projectIndexDAO.findById(project.getDocId()).orElse(null); + projectJson.setIsArchived(String.valueOf(project.isDeleted())); + return projectJson; }).filter(Objects::nonNull).collect(Collectors.toList()); } @@ -90,12 +95,14 @@ public Collection findAllByOrgId(String orgId) { } return org.get().getProjects().stream().map(project -> { ContextHolder.setContext(project.getProjectId()); - return projectIndexDAO.findById(project.getDocId()).orElse(null); + ProjectJson projectJson = projectIndexDAO.findById(project.getDocId()).orElse(null); + projectJson.setIsArchived(String.valueOf(project.isDeleted())); + return projectJson; }).filter(Objects::nonNull).collect(Collectors.toList()); } @Override - public void hardDelete(String projectId) { + public void deleteById(String projectId) { String message = ""; try { ContextHolder.clearContext(); @@ -133,7 +140,7 @@ public boolean hasPublicPermissions(String projectId) { } @Override - public void softDelete(String projectId) { + public void archiveById(String projectId) { //TODO not called locally, otherwise delete ContextHolder.setContext(projectId); Optional project = this.projectDAO.findByProjectId(projectId); @@ -152,7 +159,7 @@ public void softDelete(String projectId) { projectDAO.save(p); ProjectJson projectJson = projectJsonOption.get(); - projectJson.setIsDeleted(Constants.TRUE); + projectJson.setIsArchived(Constants.TRUE); ContextHolder.setContext(projectId); projectIndexDAO.update(projectJson); @@ -177,7 +184,7 @@ public ProjectJson save(ProjectJson projectJson) { proj.setOrganization(org.get()); proj.setProjectType(projectJson.getProjectType()); proj.setDocId(projectJson.getDocId()); - proj.setDeleted(Boolean.parseBoolean(projectJson.getIsDeleted())); + proj.setDeleted(Boolean.parseBoolean(projectJson.getIsArchived())); try { projectDAO.save(proj); @@ -188,7 +195,7 @@ public ProjectJson save(ProjectJson projectJson) { } catch (Exception e) { logger.error("Couldn't create project: {}", projectJson.getProjectId(), e); //Need to clean up in case of partial creation - hardDelete(projectJson.getProjectId()); + deleteById(projectJson.getProjectId()); throw new InternalErrorException("Could not create project"); } } @@ -212,8 +219,14 @@ public ProjectJson update(ProjectJson projectJson) { throw new BadRequestException("Invalid organization"); } } + if (projectJson.getIsArchived() != null && !projectJson.getIsArchived().isEmpty()) { + proj.setDeleted(Boolean.parseBoolean(projectJson.getIsArchived())); + } projectJson.setDocId(proj.getDocId()); + + projectDAO.save(proj); + projectJson.setIsArchived(String.valueOf(proj.isDeleted())); ContextHolder.setContext(projectJson.getProjectId()); return projectIndexDAO.update(projectJson); } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java index 1d4ecc866..c5f368d5a 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java @@ -107,7 +107,7 @@ public boolean processElementUpdated(NodeChangeInfo info, ElementJson element, E previousDocId = n.getDocId(); ((FederatedNodeChangeInfo) info).getOldDocIds().add(previousDocId); if(n.isDeleted()) { - existing.setIsDeleted(Constants.TRUE); + existing.setIsArchived(Constants.TRUE); } } else { return false; diff --git a/json/src/main/java/org/openmbee/mms/json/BaseJson.java b/json/src/main/java/org/openmbee/mms/json/BaseJson.java index b99b8b8e7..3cf194d38 100644 --- a/json/src/main/java/org/openmbee/mms/json/BaseJson.java +++ b/json/src/main/java/org/openmbee/mms/json/BaseJson.java @@ -22,7 +22,7 @@ public class BaseJson extends HashMap { public static final String CREATED = "_created"; public static final String COMMITID = "_commitId"; public static final String TYPE = "type"; - public static final String IS_DELETED = "_deleted"; + public static final String IS_ARCHIVED = "_archived"; public String getId() { return (String) this.get(ID); @@ -158,15 +158,15 @@ public T setCommitId(String commitId) { return (T) this; } - @JsonProperty(IS_DELETED) - public String getIsDeleted() { - return (String) this.get(IS_DELETED); + @JsonProperty(IS_ARCHIVED) + public String getIsArchived() { + return (String) this.get(IS_ARCHIVED) == null ? null : String.valueOf(this.get(IS_ARCHIVED)); } @SuppressWarnings("unchecked") - @JsonProperty(IS_DELETED) - public T setIsDeleted(String deleted) { - this.put(IS_DELETED, deleted); + @JsonProperty(IS_ARCHIVED) + public T setIsArchived(String archived) { + this.put(IS_ARCHIVED, archived); return (T) this; } diff --git a/json/src/main/java/org/openmbee/mms/json/OrgJson.java b/json/src/main/java/org/openmbee/mms/json/OrgJson.java index d473df280..8af94d41a 100644 --- a/json/src/main/java/org/openmbee/mms/json/OrgJson.java +++ b/json/src/main/java/org/openmbee/mms/json/OrgJson.java @@ -6,4 +6,10 @@ @JsonIgnoreProperties({BaseJson.COMMITID, BaseJson.REFID, BaseJson.PROJECTID, BaseJson.TYPE, "empty"}) @Schema(name = "Org", requiredProperties = {BaseJson.NAME}) public class OrgJson extends BaseJson { + public static final String DELETED = "deleted"; + + public OrgJson setDeleted(boolean deleted) { + this.put(DELETED, deleted); + return this; + } } diff --git a/json/src/main/java/org/openmbee/mms/json/ProjectJson.java b/json/src/main/java/org/openmbee/mms/json/ProjectJson.java index 7fdcd852a..20aaf0279 100644 --- a/json/src/main/java/org/openmbee/mms/json/ProjectJson.java +++ b/json/src/main/java/org/openmbee/mms/json/ProjectJson.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.AccessMode; @JsonIgnoreProperties({BaseJson.REFID, BaseJson.COMMITID, BaseJson.TYPE, "empty"}) @Schema(name = "Project", requiredProperties = {ProjectJson.ORGID, BaseJson.NAME}) @@ -39,4 +40,9 @@ public ProjectJson setOrgId(String orgId) { this.put(ORGID, orgId); return this; } + + public ProjectJson setDeleted(boolean deleted) { + this.put(DELETED, deleted); + return this; + } } diff --git a/json/src/main/java/org/openmbee/mms/json/UserJson.java b/json/src/main/java/org/openmbee/mms/json/UserJson.java index 24bc6b9fe..05273cf0c 100644 --- a/json/src/main/java/org/openmbee/mms/json/UserJson.java +++ b/json/src/main/java/org/openmbee/mms/json/UserJson.java @@ -1,9 +1,12 @@ package org.openmbee.mms.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.AccessMode; -@JsonIgnoreProperties({BaseJson.COMMITID, BaseJson.REFID, BaseJson.PROJECTID, BaseJson.TYPE, BaseJson.IS_DELETED, +@JsonIgnoreProperties({BaseJson.COMMITID, BaseJson.REFID, BaseJson.PROJECTID, BaseJson.IS_ARCHIVED, BaseJson.NAME, BaseJson.ID, UserJson.PASSWORD, "empty"}) @Schema(name = "User", requiredProperties = {UserJson.USERNAME}) public class UserJson extends BaseJson { @@ -15,6 +18,9 @@ public class UserJson extends BaseJson { public static final String EMAIL = "email"; public static final String PASSWORD = "password"; public static final String ENABLED = "enabled"; + public static final String CREATED = "created"; + public static final String MODIFIED = "modified"; + public static final String DISTINGUSHED_NAME = "_distingushedName"; public String getUsername() { return (String) get(USERNAME); @@ -79,4 +85,20 @@ public UserJson setEnabled(Boolean enabled) { return this; } + @JsonProperty(MODIFIED) + @Schema(accessMode = AccessMode.READ_ONLY) + @Override + public String getModified() { + return (String) this.get(MODIFIED); + } + + public String getDistingushedName() { + return (String) this.get(DISTINGUSHED_NAME); + } + + public UserJson setDistingushedName(String distingushedName) { + put(DISTINGUSHED_NAME, distingushedName); + return this; + } + } diff --git a/ldap/ldap.gradle b/ldap/ldap.gradle index bb76b1ccb..60bf621ec 100644 --- a/ldap/ldap.gradle +++ b/ldap/ldap.gradle @@ -1,6 +1,7 @@ dependencies { implementation project(':rdb') implementation project(':users') + implementation project(':localauth') implementation commonDependencies.'spring-security-ldap' } diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java similarity index 93% rename from ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java rename to ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java index 9bb98618d..23b901670 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/LdapCondition.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapCondition.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.ldap; +package org.openmbee.mms.ldap.config; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java similarity index 77% rename from ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java rename to ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java index cbffaf1eb..599af8d17 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.ldap; +package org.openmbee.mms.ldap.config; import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.dao.GroupPersistence; @@ -6,6 +6,7 @@ import org.openmbee.mms.core.dao.UserPersistence; import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.ldap.security.LdapUsersDetailsService; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -19,17 +20,23 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.ldap.core.DirContextOperations; +import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.support.BaseLdapPathContextSource; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.ldap.filter.*; import org.springframework.ldap.support.LdapEncoder; import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer; +import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.ldap.SpringSecurityLdapTemplate; +import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider; +import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -64,6 +71,9 @@ public class LdapSecurityConfig { @Value("#{'${ldap.user.dn.pattern:uid={0}}'.split(';')}") private List userDnPattern; + @Value("${ldap.user.attributes.dn:entrydn}") + private String userAttributesDn; + @Value("${ldap.user.attributes.username:uid}") private String userAttributesUsername; @@ -96,7 +106,8 @@ public class LdapSecurityConfig { private UserPersistence userPersistence; private GroupPersistence groupPersistence; private UserGroupsPersistence userGroupsPersistence; - + private LdapUsersDetailsService ldapUsersDetailsService; + @Autowired public void setUserPersistence(UserPersistence userPersistence) { this.userPersistence = userPersistence; @@ -112,6 +123,11 @@ public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence this.userGroupsPersistence = userGroupsPersistence; } + @Autowired + public void setLdapUsersDetailsService(LdapUsersDetailsService ldapUsersDetailsService) { + this.ldapUsersDetailsService = ldapUsersDetailsService; + } + @Autowired public void configureLdapAuth(AuthenticationManagerBuilder auth, LdapAuthoritiesPopulator ldapAuthoritiesPopulator, @Qualifier("contextSource") BaseLdapPathContextSource contextSource) @@ -142,7 +158,7 @@ We redefine our own LdapAuthoritiesPopulator which need ContextSource(). } @Bean - LdapAuthoritiesPopulator ldapAuthoritiesPopulator(@Qualifier("contextSource") BaseLdapPathContextSource baseContextSource) { + LdapAuthoritiesPopulator ldapAuthoritiesPopulator() { /* Specificity here : we don't get the Role by reading the members of available groups (which is implemented by @@ -150,37 +166,18 @@ LdapAuthoritiesPopulator ldapAuthoritiesPopulator(@Qualifier("contextSource") Ba */ class CustomLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator { - final SpringSecurityLdapTemplate ldapTemplate; + @Autowired + private SpringSecurityLdapTemplate ldapTemplate; - private CustomLdapAuthoritiesPopulator(BaseLdapPathContextSource ldapContextSource) { - ldapTemplate = new SpringSecurityLdapTemplate(ldapContextSource); - } + private CustomLdapAuthoritiesPopulator() {} @Override public Collection getGrantedAuthorities( DirContextOperations userData, String username) { logger.debug("Populating authorities using LDAP"); - Optional userOptional = userPersistence.findByUsername(username); - - if (userOptional.isEmpty()) { - logger.info("No user record for {} in the userRepository, creating...", userData.getDn()); - UserJson newUser = createLdapUser(userData); - userOptional = Optional.of(newUser); - } + UserJson user = ldapUsersDetailsService.loadUserByUsername(username).getUser(); - UserJson user = userOptional.get(); - if (user.getModified() != null && Instant.parse(user.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { - saveLdapUser(userData, user); - } - user.setPassword(null); - - StringBuilder userDnBuilder = new StringBuilder(); - userDnBuilder.append(userData.getDn().toString()); - if (providerBase != null && !providerBase.isEmpty()) { - userDnBuilder.append(','); - userDnBuilder.append(providerBase); - } - String userDn = userDnBuilder.toString(); + String userDn = userData.getStringAttribute(userAttributesDn); Collection definedGroups = groupPersistence.findAll(); OrFilter orFilter = new OrFilter(); @@ -201,7 +198,6 @@ public Collection getGrantedAuthorities( .searchForSingleAttributeValues(groupSearchBase, filter, new Object[]{""}, groupRoleAttribute); logger.debug("LDAP search result: {}", Arrays.toString(memberGroups.toArray())); - userPersistence.save(user); //Add groups to user Set addGroups = new HashSet<>(); @@ -231,12 +227,34 @@ public Collection getGrantedAuthorities( } } - return new CustomLdapAuthoritiesPopulator(baseContextSource); + return new CustomLdapAuthoritiesPopulator(); } @Bean public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() { + + class CustomActiveDirectoryLdapAuthenticationProvider implements AuthenticationProvider { + + private final ActiveDirectoryLdapAuthenticationProvider provider; + + public CustomActiveDirectoryLdapAuthenticationProvider(ActiveDirectoryLdapAuthenticationProvider provider) { + this.provider = provider; + + } + + @Override + public Authentication authenticate(Authentication authentication) { + Authentication auth = provider.authenticate(authentication); + return UsernamePasswordAuthenticationToken.authenticated(auth, null, ldapUsersDetailsService.getUserAuthorties(((UserDetails) auth.getPrincipal()).getUsername())); + } + + @Override + public boolean supports(Class authentication) { + return provider.supports(authentication); + } + + } ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(adDomain, providerUrl, providerBase); Hashtable env = new Hashtable<>(); @@ -251,7 +269,7 @@ public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() { provider.setSearchFilter(userSearchFilter); provider.setConvertSubErrorCodesToExceptions(true); provider.setUseAuthenticationRequestCredentials(true); - return provider; + return new CustomActiveDirectoryLdapAuthenticationProvider(provider); } @Bean @@ -271,33 +289,10 @@ public LdapContextSource contextSource() { return contextSource; } - private UserJson saveLdapUser(DirContextOperations userData, UserJson saveUser) { - if (saveUser.getEmail() == null || - !saveUser.getEmail().equals(userData.getStringAttribute(userAttributesEmail)) - ) { - saveUser.setEmail(userData.getStringAttribute(userAttributesEmail)); - } - if (saveUser.getFirstName() == null || - !saveUser.getFirstName().equals(userData.getStringAttribute(userAttributesFirstName)) - ) { - saveUser.setFirstName(userData.getStringAttribute(userAttributesFirstName)); - } - if (saveUser.getLastName() == null || - !saveUser.getLastName().equals(userData.getStringAttribute(userAttributesLastName)) - ) { - saveUser.setLastName(userData.getStringAttribute(userAttributesLastName)); - } - - return saveUser; + @Bean + public SpringSecurityLdapTemplate ldapTemplate() { + return new SpringSecurityLdapTemplate(contextSource()); } - private UserJson createLdapUser(DirContextOperations userData) { - String username = userData.getStringAttribute(userAttributesUsername); - logger.debug("Creating user for {} using LDAP", username); - UserJson user = saveLdapUser(userData, new UserJson()); - user.setUsername(username); - user.setEnabled(true); - user.setAdmin(false); - return userPersistence.save(user); - } -} \ No newline at end of file + +} diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java new file mode 100644 index 000000000..c5c341a4e --- /dev/null +++ b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java @@ -0,0 +1,348 @@ +package org.openmbee.mms.ldap.security; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import javax.naming.ldap.LdapName; + +import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.dao.GroupPersistence; +import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.ldap.config.LdapCondition; +import org.openmbee.mms.ldap.config.LdapSecurityConfig; +import org.openmbee.mms.localauth.security.LocalUsersDetailsService; +import org.openmbee.mms.users.security.DefaultUsersDetails; +import org.openmbee.mms.users.security.DefaultUsersDetailsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Conditional; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.filter.AndFilter; +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.filter.HardcodedFilter; +import org.springframework.ldap.filter.OrFilter; +import org.springframework.ldap.support.LdapEncoder; +import org.springframework.ldap.support.LdapNameBuilder; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.ldap.SpringSecurityLdapTemplate; +import org.springframework.stereotype.Service; + +@Service +@Conditional(LdapCondition.class) +public class LdapUsersDetailsService extends DefaultUsersDetailsService { + + private static Logger logger = LoggerFactory.getLogger(LdapUsersDetailsService.class); + + + public static final String TYPE = "ldap"; + + @Value("${ldap.provider.base:#{null}}") + private String providerBase; + + @Value("${ldap.user.attributes.objectClass:user}") + private String userAttributesUserClass; + + @Value("${ldap.user.attributes.objectCategory:person}") + private String userAttributesUserCategory; + + @Value("${ldap.user.attributes.username:uid}") + private String userAttributesUsername; + + @Value("${ldap.user.attributes.firstname:givenname}") + private String userAttributesFirstName; + + @Value("${ldap.user.attributes.lastname:sn}") + private String userAttributesLastName; + + @Value("${ldap.user.attributes.dn:entrydn}") + private String userAttributesDn; + + @Value("${ldap.user.attributes.email:mail}") + private String userAttributesEmail; + + @Value("${ldap.user.attributes.update:24}") + private int userAttributesUpdate; + + @Value("${ldap.user.search.base:#{''}}") + private String userSearchBase; + + @Value("${ldap.group.role.attribute:cn}") + private String groupRoleAttribute; + + @Value("${ldap.group.search.base:#{''}}") + private String groupSearchBase; + + @Value("${ldap.group.search.filter:(uniqueMember={0})}") + private String groupSearchFilter; + + @Autowired + private SpringSecurityLdapTemplate ldapTemplate; + + @Autowired + private GroupPersistence groupPersistence; + + @Override + public void changeUserPassword(String username, String password, boolean asAdmin) { + Optional userOptional = userPersistence.findByUsername(username); + if(userOptional.isEmpty()) { + throw new UsernameNotFoundException( + String.format("No user found with username '%s'.", username)); + } + + UserJson user = userOptional.get(); + if (user.getType() == LocalUsersDetailsService.TYPE) { + super.changeUserPassword(username, password, asAdmin); + } else { + throw new BadRequestException("Unable to change passwords for non-local users"); + } + } + + public UserJson update(DirContextOperations userData, UserJson user) { + + if (user.getEmail() == null || + !user.getEmail().equals(userData.getStringAttribute(userAttributesEmail)) + ) { + String email = userData.getStringAttribute(userAttributesEmail); + user.setEmail(email); + } + if (user.getFirstName() == null || + !user.getFirstName().equals(userData.getStringAttribute(userAttributesFirstName)) + ) { + user.setFirstName(userData.getStringAttribute(userAttributesFirstName)); + } + if (user.getLastName() == null || + !user.getLastName().equals(userData.getStringAttribute(userAttributesLastName)) + ) { + user.setLastName(userData.getStringAttribute(userAttributesLastName)); + } + if (userData.getStringAttribute(userAttributesDn) != null || + !user.getLastName().equals(userData.getStringAttribute(userAttributesDn)) + ) { + user.setDistingushedName(userData.getStringAttribute(userAttributesDn)); + } + + return user; + } + + + + public UserJson register(DirContextOperations userData) { + return saveUser(create(userData)); + } + + @Override + public DefaultUsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Optional user = userPersistence.findByUsername(username); + UserJson userJson = new UserJson(); + + + if (user.isEmpty()) { + userJson = saveUser(getUser(username)); + } else { + userJson = user.get(); + if (user.get().getType().equals(LdapUsersDetailsService.TYPE) && userJson.getModified() != null && Instant.parse(userJson.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { + userJson = saveUser(getUser(username)); + } + } + return new DefaultUsersDetails(userJson, userGroupsPersistence.findGroupsAssignedToUser(username)); + } + + + private UserJson getUser(String username) { + AndFilter filter = new AndFilter(); + filter.and(new EqualsFilter("objectclass", userAttributesUserClass)) + .and(new EqualsFilter("objectcategory", userAttributesUserCategory)) + .and(new EqualsFilter(userAttributesUsername, username)); + List users = ldapTemplate.search(userSearchBase, filter.encode(), new UserAttributesMapper()); + if (users.isEmpty()) { + throw new UsernameNotFoundException( + String.format("No user found with username '%s'.", username)); + } + return users.get(0); + } + + private UserJson create(DirContextOperations userData) { + String username = userData.getStringAttribute(userAttributesUsername); + UserJson user = new UserJson(); + user.setUsername(username); + user.setEnabled(true); + user.setAdmin(false); + user.setType(LdapUsersDetailsService.TYPE); + user.setPassword(null); + return update(userData, user); + } + + public Collection getUserAuthorties(String username) { + Collection definedGroups = groupPersistence.findAll(); + OrFilter orFilter = new OrFilter(); + UserJson user = getUser(username); + for (GroupJson definedGroup : definedGroups) { + orFilter.or(new EqualsFilter(groupRoleAttribute, definedGroup.getName())); + } + String userDn = user.getDistingushedName(); + AndFilter andFilter = new AndFilter(); + HardcodedFilter groupsFilter = new HardcodedFilter( + groupSearchFilter.replace("{0}", LdapEncoder.filterEncode(userDn))); + andFilter.and(groupsFilter); + andFilter.and(orFilter); + String filter = andFilter.encode(); + Set memberGroups = ldapTemplate + .searchForSingleAttributeValues(groupSearchBase, filter, new Object[]{""}, groupRoleAttribute); + logger.debug("LDAP search result: {}", Arrays.toString(memberGroups.toArray())); + + //Add groups to user + + Set addGroups = new HashSet<>(); + + for (String memberGroup : memberGroups) { + Optional group = groupPersistence.findByName(memberGroup); + group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), user.getUsername())); + group.ifPresent(addGroups::add); + } + + if (logger.isDebugEnabled()) { + if ((long) addGroups.size() > 0) { + addGroups.forEach(group -> logger.debug("Group received: {}", group.getName())); + } else { + logger.debug("No configured groups returned from LDAP"); + } + } + + + List auths = AuthorityUtils + .createAuthorityList(memberGroups.toArray(new String[0])); + if (Boolean.TRUE.equals(user.isAdmin())) { + auths.add(new SimpleGrantedAuthority(AuthorizationConstants.MMSADMIN)); + } + auths.add(new SimpleGrantedAuthority(AuthorizationConstants.EVERYONE)); + return auths; + } + + private class UserAttributesMapper implements ContextMapper { + + + public UserJson mapFromContext(Object ctx) throws NamingException { + DirContextAdapter context = (DirContextAdapter)ctx; + if (context == null) { + return null; + } + return create(context); + // if (attributes.get("objectclass") != null) { + // user.setObjectclass(attributes.get("objectclass").get().toString()); + // } + // if (attributes.get("distinguishedname") != null) { + // user.setDistinguishedname(attributes.get("distinguishedname").get().toString()); + // } + // if (attributes.get("userPassword") != null) { + // user.setUserPassword(attributes.get("userPassword").get().toString()); + // } + // if (attributes.get("cn") != null) { + // user.setCn(attributes.get("cn").get().toString()); + // } + // if (attributes.get("telephoneNumber") != null) { + // user.setTelephoneNumber(attributes.get("telephoneNumber").get().toString()); + // } + // if (attributes.get("lastlogoff") != null) { + // // user.setLastlogoff(DateTimeFormat.forPattern("yyyy-MM-dd + // // HH:mm:ss") + // // + // .parseDateTime(attributes.get("lastlogoff").get().toString())); + // DateTimeFormatter formatter = + // DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss"); + // DateTime dt = + // formatter.parseDateTime(attributes.get("lastlogoff").get().toString()); + // user.setLastlogoff(new DateTime( + // + // dt + // + // )); + // // } + // if (attributes.get("userprincipalname") != null) { + // user.setUserprincipalname(attributes.get("userprincipalname").get().toString()); + // } + // if (attributes.get("department") != null) { + // user.setDepartment(attributes.get("department").get().toString()); + // } + // if (attributes.get("company") != null) { + // user.setCompany(attributes.get("company").get().toString()); + // } + // if (attributes.get("mail") != null) { + // user.setMail(attributes.get("mail").get().toString()); + // } + // if (attributes.get("streetAddress") != null) { + // user.setStreetAddress(attributes.get("streetAddress").get().toString()); + // } + // if (attributes.get("st") != null) { + // user.setSt(attributes.get("st").get().toString()); + // } + // if (attributes.get("postalCode") != null) { + // user.setPostalCode(attributes.get("postalCode").get().toString()); + // } + // if (attributes.get("l") != null) { + // user.setL(attributes.get("l").get().toString()); + // } + // if (attributes.get("description") != null) { + // user.setDescription(attributes.get("description").get().toString()); + // } + // if (attributes.get("c") != null) { + // user.setC(attributes.get("c").get().toString()); + // } + // if (attributes.get("countryCode") != null) { + // user.setCountryCode(attributes.get("countryCode").get().toString()); + // } + // if (attributes.get("cn") != null) { + // user.setCn(attributes.get("cn").get().toString()); + // } + // if (attributes.get("sn") != null) { + // user.setSn(attributes.get("sn").get().toString()); + // } + // if (attributes.get("employeeID") != null) { + // user.setEmployeeId(attributes.get("employeeID").get().toString()); + // } + // if (attributes.get("lastLogon") != null) { + // // user.setLastLogon(DateTimeFormat.forPattern("yyyy-MM-dd + // // HH:mm:ss")/* + // // .parseDateTime(attributes.get("lastLogon").get().toString()));*/ + + // DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss"); + // DateTime dt = formatter.parseDateTime(attributes.get("lastLogon").get().toString()); + // user.setLastLogon(new DateTime( + + // dt + + // )); + // } + // if (attributes.get("memberof") != null) { + // user.setMemberof(attributes.get("memberof").get().toString()); + // } + // if (attributes.get("givenname") != null) { + // user.setGivenname(attributes.get("givenname").get().toString()); + // } + // if (attributes.get("logoncount") != null) { + // user.setLogoncount(attributes.get("logoncount").get().toString()); + // } + // if (attributes.get("displayName") != null) { + // user.setDisplayname(attributes.get("displayName").get().toString()); + // } + } + } + + +} diff --git a/localauth/src/main/java/org/openmbee/mms/localauth/config/AuthProviderConfig.java b/localauth/src/main/java/org/openmbee/mms/localauth/config/AuthProviderConfig.java index f9a3151e7..dabaeb2cf 100644 --- a/localauth/src/main/java/org/openmbee/mms/localauth/config/AuthProviderConfig.java +++ b/localauth/src/main/java/org/openmbee/mms/localauth/config/AuthProviderConfig.java @@ -1,7 +1,7 @@ package org.openmbee.mms.localauth.config; -import org.openmbee.mms.localauth.security.LocalUsersDetailsService; import org.openmbee.mms.users.objects.UserCreateRequest; +import org.openmbee.mms.users.security.DefaultUsersDetailsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -17,7 +17,7 @@ public class AuthProviderConfig { private static final Logger logger = LoggerFactory.getLogger(AuthProviderConfig.class); - private LocalUsersDetailsService userDetailsService; + private DefaultUsersDetailsService userDetailsService; private PasswordEncoder passwordEncoder; @Value("${mms.admin.username}") @@ -26,7 +26,7 @@ public class AuthProviderConfig { private String adminPassword; @Autowired - public void setUserDetailsService(LocalUsersDetailsService userDetailsService) { + public void setUserDetailsService(DefaultUsersDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } diff --git a/localauth/src/main/java/org/openmbee/mms/localauth/config/LocalAuthCondition.java b/localauth/src/main/java/org/openmbee/mms/localauth/config/LocalAuthCondition.java new file mode 100644 index 000000000..1980da18b --- /dev/null +++ b/localauth/src/main/java/org/openmbee/mms/localauth/config/LocalAuthCondition.java @@ -0,0 +1,15 @@ +package org.openmbee.mms.localauth.config; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class LocalAuthCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + Environment env = context.getEnvironment(); + String prop = env.getProperty("ldap.enabled"); + return (prop == null || prop.equals("false")); + } +} diff --git a/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java b/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java index aba52e988..08253ecd8 100644 --- a/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java +++ b/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java @@ -1,104 +1,10 @@ package org.openmbee.mms.localauth.security; -import java.util.Collection; -import java.util.Optional; - -import org.openmbee.mms.core.dao.UserGroupsPersistence; -import org.openmbee.mms.core.dao.UserPersistence; -import org.openmbee.mms.core.exceptions.ForbiddenException; -import org.openmbee.mms.json.UserJson; -import org.openmbee.mms.localauth.config.UserPasswordRulesConfig; -import org.openmbee.mms.users.objects.UserCreateRequest; -import org.openmbee.mms.users.security.AbstractUsersDetailsService; -import org.openmbee.mms.users.security.DefaultUsersDetails; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; +import org.openmbee.mms.localauth.config.LocalAuthCondition; +import org.openmbee.mms.users.security.DefaultUsersDetailsService; +import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Service; @Service -public class LocalUsersDetailsService extends AbstractUsersDetailsService { - - private PasswordEncoder passwordEncoder; - private UserPasswordRulesConfig userPasswordRulesConfig; - - @Autowired - public void setPasswordEncoder(PasswordEncoder passwordEncoder) { - this.passwordEncoder = passwordEncoder; - } - - @Autowired - public void setUserPasswordRulesConfig(UserPasswordRulesConfig userPasswordRulesConfig) { - this.userPasswordRulesConfig = userPasswordRulesConfig; - } - - @Override - public DefaultUsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Optional user = getUserPersistence().findByUsername(username); - - if (user.isEmpty()) { - throw new UsernameNotFoundException( - String.format("No user found with username '%s'.", username)); - } - return new DefaultUsersDetails(user.get(), getUserGroupsPersistence().findGroupsAssignedToUser(username)); - } - - - public UserJson register(UserCreateRequest req) { - UserJson user = new UserJson(); - user.setUsername(req.getUsername()); - user.setEmail(req.getEmail()); - user.setFirstName(req.getFirstName()); - user.setLastName(req.getLastName()); - user.setPassword(encodePassword(req.getPassword())); - user.setEnabled(true); - user.setAdmin(req.isAdmin()); - return saveUser(user); - } - - public void changeUserPassword(String username, String password, boolean asAdmin) { - Optional userOptional = getUserPersistence().findByUsername(username); - if(userOptional.isEmpty()) { - throw new UsernameNotFoundException( - String.format("No user found with username '%s'.", username)); - } - - UserJson user = userOptional.get(); - if(!asAdmin && !userPasswordRulesConfig.isAllowSelfSetPasswordsWhenBlank() && - (user.getPassword() == null || user.getPassword().isBlank())) { - throw new ForbiddenException("Cannot change or set passwords for external users."); - } - - //TODO password strength test? - user.setPassword(encodePassword(password)); - saveUser(user); - } - - public UserJson update(UserCreateRequest req, UserJson user) { - if (req.getEmail() != null && - !user.getEmail().equals(req.getEmail()) - ) { - user.setEmail(req.getEmail()); - } - if (req.getFirstName() != null && - !user.getFirstName().equals(req.getFirstName()) - ) { - user.setFirstName(req.getFirstName()); - } - if (req.getLastName() != null && - !user.getLastName().equals(req.getLastName()) - ) { - user.setLastName(req.getLastName()); - } - if (req.isEnabled() != null && user.isEnabled() != req.isEnabled()) - - if (req.getType() != null) { - user.setType(req.getType()); - } - return saveUser(user); - } - - private String encodePassword(String password) { - return (password != null && !password.isBlank()) ? passwordEncoder.encode(password) : null; - } -} \ No newline at end of file +@Conditional(LocalAuthCondition.class) +public class LocalUsersDetailsService extends DefaultUsersDetailsService {} \ No newline at end of file diff --git a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java index 1029f3003..126a41bac 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java +++ b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java @@ -6,7 +6,7 @@ import org.openmbee.mms.twc.config.TwcConfig; import org.openmbee.mms.twc.exceptions.TwcConfigurationException; import org.openmbee.mms.twc.utilities.AdminUtils; -import org.openmbee.mms.users.security.AbstractUsersDetailsService; +import org.openmbee.mms.users.security.DefaultUsersDetailsService; import org.openmbee.mms.users.security.DefaultUsersDetails; import org.openmbee.mms.users.security.UsersDetails; import org.springframework.beans.factory.annotation.Autowired; @@ -17,11 +17,11 @@ import java.util.*; @Service -public class TwcUserDetailsService extends AbstractUsersDetailsService { +public class TwcUserDetailsService extends DefaultUsersDetailsService { @Override public UsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Optional userOptional = getUserPersistence().findByUsername(username); + Optional userOptional = userPersistence.findByUsername(username); UserJson user; if (userOptional.isEmpty()) { @@ -29,7 +29,7 @@ public UsersDetails loadUserByUsername(String username) throws UsernameNotFoundE } else { user = userOptional.get(); } - return new DefaultUsersDetails(user, getUserGroupsPersistence().findGroupsAssignedToUser(username)); + return new DefaultUsersDetails(user, userGroupsPersistence.findGroupsAssignedToUser(username)); } public UserJson addUser(String username) { @@ -41,7 +41,6 @@ public UserJson addUser(String username) { return register(user); } - @Override public UserJson register(UserJson user) { return saveUser(user); } @@ -52,7 +51,6 @@ public void changeUserPassword(String username, String password, boolean asAdmin "Cannot Modify Password. Users for this server are controlled by Teamwork Cloud"); } - @Override public UserJson update(UserJson userData, UserJson saveUser) { if (saveUser.getEmail() == null || !saveUser.getEmail().equals(userData.getEmail()) diff --git a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java index 1d43c4251..c6f42b10b 100644 --- a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java +++ b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java @@ -7,6 +7,7 @@ import org.openmbee.mms.core.exceptions.UnauthorizedException; import org.openmbee.mms.core.utils.AuthenticationUtils; import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.users.security.DefaultUsersDetailsService; import org.openmbee.mms.users.security.UsersDetails; import org.openmbee.mms.users.security.UsersDetailsService; import org.openmbee.mms.users.objects.UserCreateRequest; @@ -14,10 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.*; @@ -28,10 +27,10 @@ @Tag(name = "Auth") public class UsersController { - private UsersDetailsService usersDetailsService; + private DefaultUsersDetailsService usersDetailsService; @Autowired - public UsersController(UsersDetailsService usersDetailsService) { + public void setUsersDetailsService(DefaultUsersDetailsService usersDetailsService) { this.usersDetailsService = usersDetailsService; } @@ -56,23 +55,29 @@ public UsersResponse createOrUpdateUser(@RequestBody UserCreateRequest req) { @GetMapping(value = "/users") @PreAuthorize("isAuthenticated()") - public UsersResponse getUsers() { - UsersResponse res = new UsersResponse(); - Collection users = usersDetailsService.getUsers(); - res.setUsers(users); - return res; - } - - @GetMapping(value = "/users/:username") - @PreAuthorize("isAuthenticated()") - public UsersResponse getUsers(@PathVariable String username) { + public UsersResponse getUsers(@RequestParam(required = false) String username) { UsersResponse res = new UsersResponse(); Collection users = new ArrayList<>(); - users.add(usersDetailsService.loadUserByUsername(username).getUser()); + if (username != null && !username.isEmpty()) { + users.add(usersDetailsService.loadUserByUsername(username).getUser()); + } else { + users.addAll(usersDetailsService.getUsers()); + } + usersDetailsService.getUsers(); res.setUsers(users); return res; } + // @GetMapping(value = "/users/:username") + // @PreAuthorize("isAuthenticated()") + // public UsersResponse getUsers(@PathVariable String username) { + // UsersResponse res = new UsersResponse(); + // Collection users = new ArrayList<>(); + // users.add(usersDetailsService.loadUserByUsername(username).getUser()); + // res.setUsers(users); + // return res; + // } + @GetMapping(value = "/whoami") @PreAuthorize("isAuthenticated()") public UsersResponse getCurrentUser() { diff --git a/users/src/main/java/org/openmbee/mms/users/security/AbstractUsersDetailsService.java b/users/src/main/java/org/openmbee/mms/users/security/AbstractUsersDetailsService.java deleted file mode 100644 index 13d00f9f3..000000000 --- a/users/src/main/java/org/openmbee/mms/users/security/AbstractUsersDetailsService.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.openmbee.mms.users.security; - -import org.openmbee.mms.core.dao.GroupPersistence; -import org.openmbee.mms.core.dao.UserGroupsPersistence; -import org.openmbee.mms.core.dao.UserPersistence; -import org.openmbee.mms.json.UserJson; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import java.util.Collection; -import java.util.Optional; - -@Service -public abstract class AbstractUsersDetailsService implements UsersDetailsService { - - private UserPersistence userPersistence; - private UserGroupsPersistence userGroupsPersistence; - private GroupPersistence groupPersistence; - - - public GroupPersistence getGroupPersistence() { - return this.groupPersistence; - } - - public UserPersistence getUserPersistence() { - return this.userPersistence; - } - - public UserGroupsPersistence getUserGroupsPersistence() { - return this.userGroupsPersistence; - } - - @Autowired - public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { - this.userGroupsPersistence = userGroupsPersistence; - } - - @Override - public UsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Optional user = getUserPersistence().findByUsername(username); - - if (user.isEmpty()) { - throw new UsernameNotFoundException( - String.format("No user found with username '%s'.", username)); - } - return new DefaultUsersDetails(user.get(), userGroupsPersistence.findGroupsAssignedToUser(username)); - } - - @Autowired - public void setUserPersistence(UserPersistence userPersistence) { - this.userPersistence = userPersistence; - } - - public UserJson saveUser(UserJson user) { - return getUserPersistence().save(user); - } - - public Collection getUsers() { - return getUserPersistence().findAll(); - } - - -} diff --git a/users/src/main/java/org/openmbee/mms/users/security/DefaultPasswordEncoderConfig.java b/users/src/main/java/org/openmbee/mms/users/security/DefaultPasswordEncoderConfig.java new file mode 100644 index 000000000..ce8ca5be3 --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/security/DefaultPasswordEncoderConfig.java @@ -0,0 +1,9 @@ +package org.openmbee.mms.users.security; +import org.springframework.context.annotation.Bean; +import org.springframework.security.crypto.password.PasswordEncoder; + +public interface DefaultPasswordEncoderConfig { + + @Bean + public PasswordEncoder passwordEncoder(); +} \ No newline at end of file diff --git a/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java b/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java new file mode 100644 index 000000000..be83d4b5b --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java @@ -0,0 +1,128 @@ +package org.openmbee.mms.users.security; + +import org.openmbee.mms.core.dao.GroupPersistence; +import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.core.dao.UserPersistence; +import org.openmbee.mms.core.exceptions.ForbiddenException; +import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.users.objects.UserCreateRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Optional; + +@Service +public abstract class DefaultUsersDetailsService implements UsersDetailsService { + + public static final String TYPE = "local"; + + private PasswordEncoder passwordEncoder; + private UserPasswordRulesConfig userPasswordRulesConfig; + + protected UserPersistence userPersistence; + protected UserGroupsPersistence userGroupsPersistence; + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; + } + + @Autowired + public void setPasswordEncoder(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + @Autowired + public void setUserPasswordRulesConfig(UserPasswordRulesConfig userPasswordRulesConfig) { + this.userPasswordRulesConfig = userPasswordRulesConfig; + } + + @Override + public UsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Optional user = userPersistence.findByUsername(username); + + if (user.isEmpty()) { + throw new UsernameNotFoundException( + String.format("No user found with username '%s'.", username)); + } + return new DefaultUsersDetails(user.get(), userGroupsPersistence.findGroupsAssignedToUser(username)); + } + + + public UserJson register(UserCreateRequest req) { + UserJson user = new UserJson(); + user.setUsername(req.getUsername()); + user.setEmail(req.getEmail()); + user.setFirstName(req.getFirstName()); + user.setLastName(req.getLastName()); + user.setPassword(encodePassword(req.getPassword())); + user.setEnabled(true); + user.setAdmin(req.isAdmin()); + user.setType(DefaultUsersDetailsService.TYPE); + return saveUser(user); + } + + public void changeUserPassword(String username, String password, boolean asAdmin) { + Optional userOptional = userPersistence.findByUsername(username); + if(userOptional.isEmpty()) { + throw new UsernameNotFoundException( + String.format("No user found with username '%s'.", username)); + } + + UserJson user = userOptional.get(); + if(!asAdmin && !userPasswordRulesConfig.isAllowSelfSetPasswordsWhenBlank() && + (user.getPassword() == null || user.getPassword().isBlank())) { + throw new ForbiddenException("Cannot change or set passwords for external users."); + } + + //TODO password strength test? + user.setPassword(encodePassword(password)); + saveUser(user); + } + + public UserJson update(UserCreateRequest req, UserJson user) { + if (req.getEmail() != null && + !user.getEmail().equals(req.getEmail()) + ) { + user.setEmail(req.getEmail()); + } + if (req.getFirstName() != null && + !user.getFirstName().equals(req.getFirstName()) + ) { + user.setFirstName(req.getFirstName()); + } + if (req.getLastName() != null && + !user.getLastName().equals(req.getLastName()) + ) { + user.setLastName(req.getLastName()); + } + if (req.isEnabled() != null && user.isEnabled() != req.isEnabled()) + + if (req.getType() != null) { + user.setType(req.getType()); + } + return saveUser(user); + } + + private String encodePassword(String password) { + return (password != null && !password.isBlank()) ? passwordEncoder.encode(password) : null; + } + + @Autowired + public void setUserPersistence(UserPersistence userPersistence) { + this.userPersistence = userPersistence; + } + + public UserJson saveUser(UserJson user) { + return userPersistence.save(user); + } + + public Collection getUsers() { + return userPersistence.findAll(); + } + + +} diff --git a/localauth/src/main/java/org/openmbee/mms/localauth/config/UserPasswordRulesConfig.java b/users/src/main/java/org/openmbee/mms/users/security/UserPasswordRulesConfig.java similarity index 92% rename from localauth/src/main/java/org/openmbee/mms/localauth/config/UserPasswordRulesConfig.java rename to users/src/main/java/org/openmbee/mms/users/security/UserPasswordRulesConfig.java index d7a623821..34e8a11b0 100644 --- a/localauth/src/main/java/org/openmbee/mms/localauth/config/UserPasswordRulesConfig.java +++ b/users/src/main/java/org/openmbee/mms/users/security/UserPasswordRulesConfig.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.localauth.config; +package org.openmbee.mms.users.security; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; From 4c36ad944a27aa66fca4411754aace2d9150f4f6 Mon Sep 17 00:00:00 2001 From: Enquier Date: Fri, 3 May 2024 13:23:03 -0600 Subject: [PATCH 12/19] add stash --- .../dao/FederatedBranchPersistence.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java index bfc586066..6426311ef 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java @@ -60,6 +60,7 @@ public RefJson save(RefJson refJson) { scopedBranch.setTimestamp(Formats.FORMATTER.parse(refJson.getCreated(), Instant::from)); scopedBranch.setParentRefId(refJson.getParentRefId()); scopedBranch.setDocId(refJson.getDocId()); + scopedBranch.setDeleted(Boolean.parseBoolean(refJson.getIsArchived())); //Setup global Branch object Optional project = projectDAO.findByProjectId(refJson.getProjectId()); @@ -101,11 +102,14 @@ public RefJson save(RefJson refJson) { @Override public RefJson update(RefJson refJson) { ContextHolder.setContext(refJson.getProjectId()); - Optional existing = branchDAO.findByBranchId(refJson.getId()); - existing.get().setDeleted(refJson.isDeleted()); - branchDAO.save(existing.get()); - branchIndexDAO.update(refJson); - return refJson; + Optional optionalExisting = branchDAO.findByBranchId(refJson.getId()); + Branch existing = optionalExisting.get(); + if (refJson.getIsArchived() != null) { + existing.setDeleted(Boolean.parseBoolean(refJson.getIsArchived())); + } + + branchDAO.save(existing); + return branchIndexDAO.update(refJson); } @Override From 6cc2e30fb7fb36acad29893c8789d1d6330e976c Mon Sep 17 00:00:00 2001 From: Enquier Date: Mon, 6 May 2024 16:00:04 -0600 Subject: [PATCH 13/19] bugfixes to group permissions --- .../services/DefaultPermissionService.java | 9 ++- .../core/utils/PermissionsDelegateUtil.java | 2 +- .../mms/data/domains/global/Group.java | 5 +- .../dao/FederatedGroupPersistence.java | 1 + .../dao/FederatedUserGroupsPersistence.java | 6 +- ...ltFederatedPermissionsDelegateFactory.java | 3 +- .../DefaultGroupPermissionsDelegate.java | 10 ++- gradlew | 0 .../controllers/LocalGroupsController.java | 75 +++++++++++++++---- .../mms/ldap/config/LdapSecurityConfig.java | 56 +------------- .../security/LdapUsersDetailsService.java | 30 +++++--- .../permissions/PermissionsController.java | 2 +- .../GroupGroupPermRepository.java | 2 +- .../mms/users/controller/UsersController.java | 12 --- 14 files changed, 114 insertions(+), 99 deletions(-) mode change 100755 => 100644 gradlew diff --git a/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java b/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java index c213f7a03..820aae5a0 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java +++ b/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java @@ -24,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -278,11 +279,13 @@ public boolean hasBranchPrivilege(String privilege, String user, Set gro @Override public boolean hasGroupPrivilege(String privilege, String user, Set groups, String groupName) { - //Return true, however admin are only allowed "delete" perms on Remote group types - if (groups.contains(AuthorizationConstants.MMSADMIN)) return true; + if (privilege.equals("GROUP_READ") && groupName.equals(AuthorizationConstants.EVERYONE)) { + return true; + } + GroupJson group = getGroup(groupName); - + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(group); return permissionsDelegate.hasPermission(user, groups, privilege); } diff --git a/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java b/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java index 9c98f4415..909050b9f 100644 --- a/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java +++ b/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java @@ -78,7 +78,7 @@ public PermissionsDelegate getPermissionsDelegate(final GroupJson group) { } throw new InternalErrorException( - "No valid permissions scheme found for project " + group.getName() + "No valid permissions scheme found for group " + group.getName() + " (" + group.getName() + ")"); } } diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java b/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java index a489079dd..ed2d289c2 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java @@ -35,7 +35,10 @@ public class Group extends Base { @JsonProperty("public") private boolean isPublic; - public Group() {} + public Group() { + this.type = VALID_GROUP_TYPES.REMOTE; + this.typeString = VALID_GROUP_TYPES.REMOTE.toString().toLowerCase(); + } public Group(String name) { this.name = name; this.type = VALID_GROUP_TYPES.REMOTE; diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java index 52f8c4a94..4fbc80daa 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java @@ -35,6 +35,7 @@ public void setJsonUtils(FederatedJsonUtils jsonUtils) { public GroupJson save(GroupJson groupJson) { Group groupObj = new Group(); groupObj.setName(groupJson.getName()); + groupObj.setType(groupJson.getType()); Group saved = groupRepository.saveAndFlush(groupObj); return getJson(saved); } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserGroupsPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserGroupsPersistence.java index 4cb9de80d..5c8ff6ee5 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserGroupsPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserGroupsPersistence.java @@ -1,5 +1,6 @@ package org.openmbee.mms.federatedpersistence.dao; +import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.dao.UserGroupsPersistence; import org.openmbee.mms.data.domains.global.Group; import org.openmbee.mms.data.domains.global.User; @@ -83,8 +84,11 @@ public boolean removeUserFromGroup(String groupName, String username) { @Override @Transactional public Collection findUsersInGroup(String groupName) { + if (groupName.equals(AuthorizationConstants.EVERYONE)) { + return userRepository.findAll().stream().map(this::getJson).collect(Collectors.toList()); + } Optional groupOptional = groupRepository.findByName(groupName); - if(groupOptional.isEmpty()){ + if(groupOptional.isEmpty()) { return List.of(); } return groupOptional.get().getUsers().stream().map(this::getJson).collect(Collectors.toList()); diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java index cb28dd4f6..a1f884394 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java @@ -86,11 +86,10 @@ public PermissionsDelegate getPermissionsDelegate(RefJson branch) { @Override public PermissionsDelegate getPermissionsDelegate(GroupJson group) { - ContextHolder.setContext(null); Optional groupOptional = groupRepository.findByName(group.getName()); if(groupOptional.isEmpty()) { throw new NotFoundException("group not found"); } - return applicationContext.getBean(DefaultBranchPermissionsDelegate.class, groupOptional.get()); + return applicationContext.getBean(DefaultGroupPermissionsDelegate.class, groupOptional.get()); } } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java index c80a49dc2..97718971a 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java @@ -16,13 +16,17 @@ import org.openmbee.mms.rdb.repositories.GroupGroupPermRepository; import org.openmbee.mms.rdb.repositories.GroupUserPermRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; import org.springframework.data.util.Pair; import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.Optional; import java.util.Set; +@Component +@Scope(value = "prototype") public class DefaultGroupPermissionsDelegate extends AbstractDefaultPermissionsDelegate { private GroupUserPermRepository groupUserPermRepo; @@ -46,17 +50,21 @@ public void setGroupGroupPermRepo(GroupGroupPermRepository groupGroupPermRepo) { @Override public boolean hasPermission(String user, Set groupPerms, String privilege) { - Optional priv = getPrivRepo().findByName(privilege); if (priv.isEmpty()) { throw new PermissionException(HttpStatus.BAD_REQUEST, "No such privilege"); } + //Return false if group is remotely managed if (group.getType().equals(Group.VALID_GROUP_TYPES.REMOTE) && privilege.equalsIgnoreCase("GROUP_EDIT")) { throw new PermissionException(HttpStatus.BAD_REQUEST, "Unable to edit remote groups."); } + if (groupPerms.contains(AuthorizationConstants.MMSADMIN)) { + return true; + } + Set roles = priv.get().getRoles(); if (groupUserPermRepo.existsByGroupAndUser_UsernameAndRoleIn(group, user, roles)) { return true; diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java index 76c5168d1..6be314e8b 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java +++ b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java @@ -5,15 +5,12 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.config.Constants; import org.openmbee.mms.core.config.Privileges; import org.openmbee.mms.core.dao.GroupPersistence; import org.openmbee.mms.core.dao.UserGroupsPersistence; -import org.openmbee.mms.core.dao.UserPersistence; import org.openmbee.mms.core.exceptions.*; import org.openmbee.mms.core.objects.*; import org.openmbee.mms.core.security.MethodSecurityService; @@ -22,16 +19,13 @@ import org.openmbee.mms.groups.objects.*; import org.openmbee.mms.groups.services.GroupValidationService; import org.openmbee.mms.json.GroupJson; -import org.openmbee.mms.json.OrgJson; import org.openmbee.mms.json.UserJson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Sort; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.User; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @@ -48,7 +42,6 @@ public class LocalGroupsController { private GroupPersistence groupPersistence; private GroupValidationService groupValidationService; - private UserPersistence userPersistence; private UserGroupsPersistence userGroupsPersistence; protected PermissionService permissionService; @@ -89,9 +82,63 @@ public void setObjectMapper(ObjectMapper om) { this.om = om; } - @Autowired - public void setUserPersistence(UserPersistence userPersistence) { - this.userPersistence = userPersistence; + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + @Transactional + @PreAuthorize("isAuthenticated()") + public GroupsResponse createOrUpdateGroups( + @RequestBody GroupsRequest groupPost, + Authentication auth) { + + GroupsResponse response = new GroupsResponse(); + if (groupPost.getGroups().isEmpty()) { + throw new BadRequestException(response.addMessage("No groups provided")); + } + + for (GroupJson group : groupPost.getGroups()) { + + if (group.getName() == null || group.getName().isEmpty()) { + group.setName(UUID.randomUUID().toString()); + } + + if (!groupValidationService.isValidGroupName(group.getName())) { + throw new BadRequestException(GroupConstants.INVALID_GROUP_NAME); + } + + if (group.getType() == null || group.getType().isEmpty()) { + throw new BadRequestException(response.addMessage("No type provided for group:" + group.getName())); + } + + Optional optG = groupPersistence.findByName(group.getName()); + boolean newGroup = true; + GroupJson g = new GroupJson(); + if (optG.isPresent()) { + newGroup = false; + g = optG.get(); + if (!mss.hasGroupPrivilege(auth, g.getName(), Privileges.GROUP_EDIT.name(), false)) { + response.addRejection(new Rejection(group, 403, GroupConstants.NO_PERMISSSION)); + continue; + } + if (!g.getType().equals("local")) { + response.addRejection(new Rejection(group, 403, "Unable to update non-local groups")); + } + if (!group.getType().equals(g.getType()) && !(userGroupsPersistence.findUsersInGroup(g.getName()) == null || userGroupsPersistence.findUsersInGroup(g.getName()).isEmpty())) { + response.addRejection(new Rejection(group, 403, GroupConstants.GROUP_NOT_EMPTY)); + } + } + g.setName(group.getName()); + g.setType(group.getType()); + logger.info("Saving group: {}", g.getName()); + GroupJson saved = groupPersistence.save(g); + if (newGroup) { + permissionService.initGroupPerms(group.getName(), auth.getName()); + } + group.merge(convertToMap(saved)); + response.getGroups().add(group); + } + if (groupPost.getGroups().size() == 1) { + handleSingleResponse(response); + } + return response; } @PutMapping("/{group}") @@ -120,7 +167,7 @@ public GroupsResponse getAllGroups( GroupsResponse response = new GroupsResponse(); Collection allGroups = groupPersistence.findAll(); for (GroupJson group : allGroups) { - if (mss.hasOrgPrivilege(auth, group.getName(), Privileges.GROUP_READ.name(), false)) { + if (mss.hasGroupPrivilege(auth, group.getName(), Privileges.GROUP_READ.name(), false)) { response.getGroups().add(group); } } @@ -129,9 +176,9 @@ public GroupsResponse getAllGroups( @GetMapping(value = "/{group}") @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_READ', true)") - public GroupResponse getGroup(@PathVariable String group) { + public GroupResponse getGroup(@PathVariable String group, @RequestParam(required = false, defaultValue = Constants.TRUE) boolean users) { return new GroupResponse(groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)), - userGroupsPersistence.findUsersInGroup(group).stream().map(UserJson::getUsername).collect(Collectors.toList())); + users ? userGroupsPersistence.findUsersInGroup(group).stream().map(UserJson::getUsername).collect(Collectors.toList()) : null); } @DeleteMapping("/{group}") diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java index 599af8d17..b86c4e85d 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/config/LdapSecurityConfig.java @@ -33,6 +33,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.ldap.SpringSecurityLdapTemplate; import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider; @@ -166,64 +167,13 @@ LdapAuthoritiesPopulator ldapAuthoritiesPopulator() { */ class CustomLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator { - @Autowired - private SpringSecurityLdapTemplate ldapTemplate; - private CustomLdapAuthoritiesPopulator() {} @Override public Collection getGrantedAuthorities( DirContextOperations userData, String username) { logger.debug("Populating authorities using LDAP"); - UserJson user = ldapUsersDetailsService.loadUserByUsername(username).getUser(); - - String userDn = userData.getStringAttribute(userAttributesDn); - Collection definedGroups = groupPersistence.findAll(); - OrFilter orFilter = new OrFilter(); - - for (GroupJson definedGroup : definedGroups) { - orFilter.or(new EqualsFilter(groupRoleAttribute, definedGroup.getName())); - } - - AndFilter andFilter = new AndFilter(); - HardcodedFilter groupsFilter = new HardcodedFilter( - groupSearchFilter.replace("{0}", LdapEncoder.filterEncode(userDn))); - andFilter.and(groupsFilter); - andFilter.and(orFilter); - - String filter = andFilter.encode(); - logger.debug("Searching LDAP with filter: {}", filter); - - Set memberGroups = ldapTemplate - .searchForSingleAttributeValues(groupSearchBase, filter, new Object[]{""}, groupRoleAttribute); - logger.debug("LDAP search result: {}", Arrays.toString(memberGroups.toArray())); - - //Add groups to user - - Set addGroups = new HashSet<>(); - - for (String memberGroup : memberGroups) { - Optional group = groupPersistence.findByName(memberGroup); - group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), user.getUsername())); - group.ifPresent(addGroups::add); - } - - if (logger.isDebugEnabled()) { - if ((long) addGroups.size() > 0) { - addGroups.forEach(group -> logger.debug("Group received: {}", group.getName())); - } else { - logger.debug("No configured groups returned from LDAP"); - } - } - - - List auths = AuthorityUtils - .createAuthorityList(memberGroups.toArray(new String[0])); - if (Boolean.TRUE.equals(user.isAdmin())) { - auths.add(new SimpleGrantedAuthority(AuthorizationConstants.MMSADMIN)); - } - auths.add(new SimpleGrantedAuthority(AuthorizationConstants.EVERYONE)); - return auths; + return ldapUsersDetailsService.getUserAuthorities(username); } } @@ -246,7 +196,7 @@ public CustomActiveDirectoryLdapAuthenticationProvider(ActiveDirectoryLdapAuthen @Override public Authentication authenticate(Authentication authentication) { Authentication auth = provider.authenticate(authentication); - return UsernamePasswordAuthenticationToken.authenticated(auth, null, ldapUsersDetailsService.getUserAuthorties(((UserDetails) auth.getPrincipal()).getUsername())); + return UsernamePasswordAuthenticationToken.authenticated(auth.getPrincipal(), auth.getCredentials(), ldapUsersDetailsService.getUserAuthorities(((UserDetails) auth.getPrincipal()).getUsername())); } @Override diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java index c5c341a4e..85c9190e8 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java @@ -189,34 +189,46 @@ private UserJson create(DirContextOperations userData) { return update(userData, user); } - public Collection getUserAuthorties(String username) { + public Collection getUserAuthorities(String username) { Collection definedGroups = groupPersistence.findAll(); OrFilter orFilter = new OrFilter(); UserJson user = getUser(username); for (GroupJson definedGroup : definedGroups) { - orFilter.or(new EqualsFilter(groupRoleAttribute, definedGroup.getName())); + //Add the group to the list if it's from LDAP (or null just to be safe) + if (definedGroup.getType().equals(LdapUsersDetailsService.TYPE) || definedGroup.getType() == null){ + orFilter.or(new EqualsFilter(groupRoleAttribute, definedGroup.getName())); + } } String userDn = user.getDistingushedName(); AndFilter andFilter = new AndFilter(); - HardcodedFilter groupsFilter = new HardcodedFilter( - groupSearchFilter.replace("{0}", LdapEncoder.filterEncode(userDn))); + HardcodedFilter groupsFilter = new HardcodedFilter( + "(" + groupSearchFilter.replace("{0}", LdapEncoder.filterEncode(userDn)) + ")"); andFilter.and(groupsFilter); andFilter.and(orFilter); String filter = andFilter.encode(); + Set allGroups = ldapTemplate + .searchForSingleAttributeValues(groupSearchBase, orFilter.encode(), new Object[]{""}, groupRoleAttribute); Set memberGroups = ldapTemplate .searchForSingleAttributeValues(groupSearchBase, filter, new Object[]{""}, groupRoleAttribute); logger.debug("LDAP search result: {}", Arrays.toString(memberGroups.toArray())); //Add groups to user - Set addGroups = new HashSet<>(); + //Set addGroups = new HashSet<>(); - for (String memberGroup : memberGroups) { - Optional group = groupPersistence.findByName(memberGroup); - group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), user.getUsername())); - group.ifPresent(addGroups::add); + //Update user membershup to any groups that were found to match + for (String ldapGroup : allGroups) { + Optional group = groupPersistence.findByName(ldapGroup); + if (memberGroups.contains(ldapGroup)) { + group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), user.getUsername())); + } else { + group.ifPresent(g -> userGroupsPersistence.removeUserFromGroup(g.getName(), user.getUsername())); + } } + //Finally calculate total group authority using UserGroupsPersistence + Collection addGroups = userGroupsPersistence.findGroupsAssignedToUser(user.getUsername()); + if (logger.isDebugEnabled()) { if ((long) addGroups.size() > 0) { addGroups.forEach(group -> logger.debug("Group received: {}", group.getName())); diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java index 7b6ba925f..140e9779b 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java +++ b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java @@ -149,7 +149,7 @@ public PermissionsResponse getBranchPermissions( } @GetMapping(value = "/groups/{group}/permissions") - @PreAuthorize("@mss.hasGroupPrivilege(authentication, #groupName, 'GROUP_READ_PERMISSIONS', true)") + @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_READ_PERMISSIONS', true)") public PermissionsResponse getGroupPermissions( @PathVariable String group) { diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupGroupPermRepository.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupGroupPermRepository.java index 26a9d65cc..410a320cc 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupGroupPermRepository.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/GroupGroupPermRepository.java @@ -17,7 +17,7 @@ public interface GroupGroupPermRepository extends JpaRepository findAllByGroup_Name(String group); - Optional findByGroupAndGroupPerm( Group group, Group groupPerm); + Optional findByGroupAndGroupPerm(Group group, Group groupPerm); List findAllByGroupAndRole_Name(Group group, String r); diff --git a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java index c6f42b10b..503b8ef5c 100644 --- a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java +++ b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java @@ -2,14 +2,12 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.openmbee.mms.core.config.AuthorizationConstants; -import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.exceptions.UnauthorizedException; import org.openmbee.mms.core.utils.AuthenticationUtils; import org.openmbee.mms.json.UserJson; import org.openmbee.mms.users.security.DefaultUsersDetailsService; import org.openmbee.mms.users.security.UsersDetails; -import org.openmbee.mms.users.security.UsersDetailsService; import org.openmbee.mms.users.objects.UserCreateRequest; import org.openmbee.mms.users.objects.UsersResponse; import org.springframework.beans.factory.annotation.Autowired; @@ -68,16 +66,6 @@ public UsersResponse getUsers(@RequestParam(required = false) String username) { return res; } - // @GetMapping(value = "/users/:username") - // @PreAuthorize("isAuthenticated()") - // public UsersResponse getUsers(@PathVariable String username) { - // UsersResponse res = new UsersResponse(); - // Collection users = new ArrayList<>(); - // users.add(usersDetailsService.loadUserByUsername(username).getUser()); - // res.setUsers(users); - // return res; - // } - @GetMapping(value = "/whoami") @PreAuthorize("isAuthenticated()") public UsersResponse getCurrentUser() { From 5b51661edea93d60eed72a41253f75e325d8ebc0 Mon Sep 17 00:00:00 2001 From: Enquier Date: Mon, 20 May 2024 09:57:53 -0600 Subject: [PATCH 14/19] fix error with documents add more admin functions for users controller --- .../openmbee/mms/cameo/CameoConstants.java | 3 ++ .../mms/cameo/services/CameoViewService.java | 3 +- .../controllers/LocalGroupsController.java | 4 +- .../mms/users/controller/UsersController.java | 37 +++++++++++++---- .../mms/users/objects/UserGroupsResponse.java | 40 +++++++++++++++++++ 5 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/CameoConstants.java b/cameo/src/main/java/org/openmbee/mms/cameo/CameoConstants.java index 5893edad2..009541ac1 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/CameoConstants.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/CameoConstants.java @@ -99,6 +99,9 @@ public class CameoConstants { public static final String PACKAGE_TYPE = "Package"; public static final String PUBLIC_VISIBILITY = "public"; + + public static final String PROJECT_MODEL_SUFFIX = "_pm"; + public static final Map STEREOTYPEIDS; static { STEREOTYPEIDS = new HashMap<>(); diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java index c0749c4a6..78f60b617 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java @@ -251,7 +251,8 @@ private Optional getFirstRelationshipOfType(String projectId, Strin return next; } nextId = (String)next.get().get(relkey); - if (nextId == null || nextId.isEmpty()) { + // If there isn't a next or if the nextId would be the project root (would return empty element) + if (nextId == null || nextId.isEmpty() || next.get().getId().endsWith(CameoConstants.PROJECT_MODEL_SUFFIX)) { return Optional.empty(); } getInfo = nodePersistence.findById(projectId, refId, commitId, nextId); diff --git a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java index 6be314e8b..c29efa041 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java +++ b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java @@ -176,9 +176,9 @@ public GroupsResponse getAllGroups( @GetMapping(value = "/{group}") @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_READ', true)") - public GroupResponse getGroup(@PathVariable String group, @RequestParam(required = false, defaultValue = Constants.TRUE) boolean users) { + public GroupResponse getGroup(@PathVariable String group) { return new GroupResponse(groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)), - users ? userGroupsPersistence.findUsersInGroup(group).stream().map(UserJson::getUsername).collect(Collectors.toList()) : null); + userGroupsPersistence.findUsersInGroup(group).stream().map(UserJson::getUsername).collect(Collectors.toList())); } @DeleteMapping("/{group}") diff --git a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java index 503b8ef5c..da399404b 100644 --- a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java +++ b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java @@ -2,13 +2,16 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.dao.UserGroupsPersistence; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.exceptions.UnauthorizedException; import org.openmbee.mms.core.utils.AuthenticationUtils; +import org.openmbee.mms.json.GroupJson; import org.openmbee.mms.json.UserJson; import org.openmbee.mms.users.security.DefaultUsersDetailsService; import org.openmbee.mms.users.security.UsersDetails; import org.openmbee.mms.users.objects.UserCreateRequest; +import org.openmbee.mms.users.objects.UserGroupsResponse; import org.openmbee.mms.users.objects.UsersResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; @@ -20,6 +23,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.stream.Collectors; @RestController @Tag(name = "Auth") @@ -27,6 +31,13 @@ public class UsersController { private DefaultUsersDetailsService usersDetailsService; + private UserGroupsPersistence userGroupsPersistence; + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; + } + @Autowired public void setUsersDetailsService(DefaultUsersDetailsService usersDetailsService) { this.usersDetailsService = usersDetailsService; @@ -53,19 +64,31 @@ public UsersResponse createOrUpdateUser(@RequestBody UserCreateRequest req) { @GetMapping(value = "/users") @PreAuthorize("isAuthenticated()") - public UsersResponse getUsers(@RequestParam(required = false) String username) { + public UsersResponse getUsers() { UsersResponse res = new UsersResponse(); Collection users = new ArrayList<>(); - if (username != null && !username.isEmpty()) { - users.add(usersDetailsService.loadUserByUsername(username).getUser()); - } else { - users.addAll(usersDetailsService.getUsers()); - } - usersDetailsService.getUsers(); + users.addAll(usersDetailsService.getUsers()); res.setUsers(users); return res; } + @GetMapping(value = "/users/:username") + @PreAuthorize("isAuthenticated()") + public UsersResponse getUser(@PathVariable String username) { + UsersResponse res = new UsersResponse(); + UserJson user = usersDetailsService.loadUserByUsername(username).getUser(); + Collection users = new ArrayList<>(); + users.add(user); + res.setUsers(users); + return res; + } + + @GetMapping(value = "/users/:username/groups") + @PreAuthorize("isAuthenticated()") + public UserGroupsResponse getUserGroups(@PathVariable String username) { + return new UserGroupsResponse(usersDetailsService.loadUserByUsername(username).getUser(), userGroupsPersistence.findGroupsAssignedToUser(username).stream().map(GroupJson::getName).collect(Collectors.toList())); + } + @GetMapping(value = "/whoami") @PreAuthorize("isAuthenticated()") public UsersResponse getCurrentUser() { diff --git a/users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java b/users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java new file mode 100644 index 000000000..f5c387ade --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java @@ -0,0 +1,40 @@ +package org.openmbee.mms.users.objects; + + +import java.util.Collection; + +import org.openmbee.mms.json.UserJson; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class UserGroupsResponse { + + @Schema(required = true) + private String user; + + @Schema(nullable = true) + private Collection groups; + + public UserGroupsResponse(){} + + public UserGroupsResponse(UserJson user, Collection groups){ + this.user = user.getName(); + this.groups = groups; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public Collection getGroups() { + return groups; + } + + public void setGroups(Collection groups) { + this.groups = groups; + } +} From fdc3643cbd590223cb822475725f7c811fd98948 Mon Sep 17 00:00:00 2001 From: Enquier Date: Tue, 21 May 2024 11:12:15 -0600 Subject: [PATCH 15/19] add features for delete and api updates --- .../mms/cameo/services/CameoNodeService.java | 2 +- .../mms/cameo/services/CameoViewService.java | 7 +- .../openmbee/mms/core/config/Constants.java | 2 + .../mms/core/dao/NodePersistence.java | 6 +- .../org/openmbee/mms/crud/CrudConstants.java | 1 + .../crud/controllers/orgs/OrgsController.java | 81 ++++++++++++++++--- .../projects/ProjectsController.java | 4 +- .../crud/domain/DefaultNodeUpdateFilter.java | 2 +- .../mms/crud/domain/NodeChangeDomain.java | 1 + .../mms/crud/services/DefaultNodeService.java | 7 +- .../crud/services/DefaultProjectService.java | 4 +- .../mms/crud/services/OrgDeleteService.java | 2 +- .../crud/services/ProjectDeleteService.java | 2 +- .../mms/elastic/ProjectElasticImpl.java | 2 +- .../dao/FederatedBranchPersistence.java | 6 +- .../dao/FederatedNodePersistence.java | 12 +-- .../dao/FederatedOrgPersistence.java | 6 +- .../dao/FederatedProjectPersistence.java | 16 ++-- .../domain/FederatedNodeChangeDomain.java | 6 +- .../domain/FederatedNodeGetDomain.java | 16 +++- .../controllers/LocalGroupsController.java | 28 ++++--- .../groups/objects/GroupUsersResponse.java | 26 ------ .../mms/groups/objects/GroupsResponse.java | 4 + .../java/org/openmbee/mms/json/BaseJson.java | 8 +- .../jupyter/services/JupyterNodeService.java | 4 +- .../mms/msosa/services/MsosaNodeService.java | 2 +- .../mms/msosa/services/MsosaViewService.java | 7 +- .../mms/users/controller/UsersController.java | 4 +- .../mms/users/objects/UserGroupsResponse.java | 14 +++- 29 files changed, 179 insertions(+), 103 deletions(-) delete mode 100644 groups/src/main/java/org/openmbee/mms/groups/objects/GroupUsersResponse.java diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoNodeService.java b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoNodeService.java index 4669d1ff8..727f15d61 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoNodeService.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoNodeService.java @@ -96,7 +96,7 @@ public MountJson getProjectUsages(String projectId, String refId, String commitI boolean restrictOnPermissions) { saw.add(Pair.of(projectId, refId)); List mounts = getNodePersistence().findAllByNodeType(projectId, refId, commitId, - CameoNodeType.PROJECTUSAGE.getValue()); + CameoNodeType.PROJECTUSAGE.getValue(), false); Authentication auth = SecurityContextHolder.getContext().getAuthentication(); List mountValues = new ArrayList<>(); diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java index 78f60b617..d567f0f88 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java @@ -19,6 +19,7 @@ import org.openmbee.mms.core.objects.ElementsResponse; import org.openmbee.mms.core.services.NodeChangeInfo; import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.domain.JsonDomain; import org.openmbee.mms.json.ElementJson; import org.openmbee.mms.view.services.PropertyData; @@ -30,9 +31,10 @@ public class CameoViewService extends CameoNodeService implements ViewService { @Override public ElementsResponse getDocuments(String projectId, String refId, Map params) { + Boolean deleted = Boolean.parseBoolean(params.getOrDefault(CrudConstants.DELETED, "false")); String commitId = params.getOrDefault(CameoConstants.COMMITID, null); List documents = getNodePersistence().findAllByNodeType(projectId, refId, - commitId, CameoNodeType.DOCUMENT.getValue()); + commitId, CameoNodeType.DOCUMENT.getValue(), deleted); ElementsResponse res = this.getViews(projectId, refId, buildRequestFromJsons(documents), params); for (ElementJson e: res.getElements()) { Optional parent = getFirstRelationshipOfType(projectId, refId, commitId, e, @@ -83,9 +85,10 @@ public void addChildViews(ElementsResponse res, Map params) { } public ElementsResponse getGroups(String projectId, String refId, Map params) { + Boolean deleted = Boolean.parseBoolean(params.getOrDefault(CrudConstants.DELETED, "false")); String commitId = params.getOrDefault(CameoConstants.COMMITID, null); List groups = getNodePersistence().findAllByNodeType(projectId, refId, commitId, - CameoNodeType.GROUP.getValue()); + CameoNodeType.GROUP.getValue(), deleted); ElementsResponse res = new ElementsResponse().setElements(groups); for (ElementJson e: groups) { diff --git a/core/src/main/java/org/openmbee/mms/core/config/Constants.java b/core/src/main/java/org/openmbee/mms/core/config/Constants.java index 36c28b74b..1e2801adb 100644 --- a/core/src/main/java/org/openmbee/mms/core/config/Constants.java +++ b/core/src/main/java/org/openmbee/mms/core/config/Constants.java @@ -24,6 +24,8 @@ public class Constants { public static final String FALSE = "false"; public static final String LIMIT = "limit"; + public static final String HOME_SUFFIX = "-home"; + public static final String MASTER_BRANCH = "master"; public static final Pattern BRANCH_ID_VALID_PATTERN = Pattern.compile("^[\\w-]+$"); diff --git a/core/src/main/java/org/openmbee/mms/core/dao/NodePersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/NodePersistence.java index e06c7414c..6fc8ec5fb 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/NodePersistence.java +++ b/core/src/main/java/org/openmbee/mms/core/dao/NodePersistence.java @@ -23,13 +23,13 @@ public interface NodePersistence { NodeGetInfo findById(String projectId, String refId, String commitId, String elementId); - List findAllByNodeType(String projectId, String refId, String commitId, int nodeType); + List findAllByNodeType(String projectId, String refId, String commitId, int nodeType, Boolean deleted); NodeGetInfo findAll(String projectId, String refId, String commitId, List elements); - List findAll(String projectId, String refId, String commitId); + List findAll(String projectId, String refId, String commitId, Boolean deleted); - void streamAllAtCommit(String projectId, String refId, String commitId, OutputStream outputStream, String separator); + void streamAllAtCommit(String projectId, String refId, String commitId, OutputStream outputStream, String separator, Boolean deleted); void branchElements(RefJson parentBranch, CommitJson parentCommit, RefJson targetBranch); } diff --git a/crud/src/main/java/org/openmbee/mms/crud/CrudConstants.java b/crud/src/main/java/org/openmbee/mms/crud/CrudConstants.java index 642c8351a..41f1d36a1 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/CrudConstants.java +++ b/crud/src/main/java/org/openmbee/mms/crud/CrudConstants.java @@ -13,4 +13,5 @@ public class CrudConstants { public static final String ORG = "Org"; public static final String CREATED = "created"; public static final String CREATING = "creating"; + public static final String DELETED = "deleted"; } \ No newline at end of file diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java index 816245ad6..c5a224d15 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java @@ -16,9 +16,11 @@ import org.openmbee.mms.core.objects.OrganizationsRequest; import org.openmbee.mms.core.objects.OrganizationsResponse; import org.openmbee.mms.core.objects.Rejection; +import org.openmbee.mms.core.services.ProjectService; import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.controllers.BaseController; import org.openmbee.mms.crud.services.OrgDeleteService; +import org.openmbee.mms.crud.services.ProjectDeleteService; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.json.OrgJson; @@ -45,6 +47,8 @@ public class OrgsController extends BaseController { OrgDeleteService orgDeleteService; + ProjectDeleteService projectDeleteService; + @Autowired public OrgsController(OrgPersistence organizationRepository) { this.organizationRepository = organizationRepository; @@ -55,6 +59,11 @@ public void setOrgDeleteService(OrgDeleteService orgDeleteService) { this.orgDeleteService = orgDeleteService; } + @Autowired + public void setProjectDeleteService(ProjectDeleteService projectDeleteService) { + this.projectDeleteService = projectDeleteService; + } + @GetMapping public OrganizationsResponse getAllOrgs(@RequestParam(required = false, defaultValue = Constants.FALSE) boolean includeArchived, Authentication auth) { @@ -62,7 +71,7 @@ public OrganizationsResponse getAllOrgs(@RequestParam(required = false, defaultV Collection allOrgs = organizationRepository.findAll(); for (OrgJson org : allOrgs) { if (mss.hasOrgPrivilege(auth, org.getId(), Privileges.ORG_READ.name(), true) - && (!Constants.TRUE.equals(org.getIsArchived()) || includeArchived)) { + && (!org.isArchived() || includeArchived)) { response.getOrgs().add(org); } } @@ -119,19 +128,31 @@ public OrganizationsResponse createOrUpdateOrgs( org.setCreator(auth.getName()); } } - if (org.getIsArchived() != null && !org.getIsArchived().equals(o.getIsArchived())) { - List orgProjs = projectPersistence.findAllByOrgId(org.getId()).stream().collect(Collectors.toList()); - //Un/Archive all projects contained by org - for (ProjectJson proj: orgProjs) { - proj.setIsArchived(org.getIsArchived()); - projectPersistence.update(proj); - } - } logger.info("Saving organization: {}", org.getId()); OrgJson saved = organizationRepository.save(org); if (newOrg) { + // Initialize Org Permissions permissionService.initOrgPerms(org.getId(), auth.getName()); + + createOrgHome(org); + } else { + Collection orgProjs = projectPersistence.findAllByOrgId(org.getId()); + if (!orgProjs.stream().map(ProjectJson::getId).collect(Collectors.toList()).contains(org.getId() + Constants.HOME_SUFFIX)) { + if (org.isArchived() == null) { + org.setIsArchived(o.isArchived()); + } + createOrgHome(org); + } + for (ProjectJson proj: orgProjs) { + if (org.isArchived() != null && !org.isArchived().equals(o.isArchived())) { + //Un/Archive all projects contained by org + + proj.setIsArchived(org.isArchived()); + projectPersistence.update(proj); + } + } } + response.getOrgs().add(saved); } if (orgPost.getOrgs().size() == 1) { @@ -151,10 +172,46 @@ public OrganizationsResponse deleteOrg( if (!orgOption.isPresent()) { throw new NotFoundException(response.addMessage("Organization not found.")); } - if (!projectPersistence.findAllByOrgId(orgId).isEmpty() && hard) { - throw new BadRequestException(response.addMessage("Cannot Hard Delete Organization that contains Projects")); + if (hard) { + List orgProjs = projectPersistence.findAllByOrgId(orgId).stream().collect(Collectors.toList()); + if (!orgProjs.isEmpty()) { + if (orgProjs.size() == 1 && orgProjs.get(0).getId().equals(orgId + Constants.HOME_SUFFIX)) { + projectDeleteService.deleteProject(orgId + Constants.HOME_SUFFIX, hard); + } else { + throw new BadRequestException(response.addMessage("Cannot Hard Delete Organization that contains Projects other than its home")); + } + } } - return orgDeleteService.deleteOrg(orgId, hard); } + + private ProjectJson createOrgHome(OrgJson org) { + // Create New Org "Home" Project + ProjectJson homeProj = new ProjectJson(); + homeProj.setCreated(org.getCreated()); + homeProj.setType(CrudConstants.PROJECT); + homeProj.setCreator(org.getCreator()); + homeProj.setId(org.getId() + Constants.HOME_SUFFIX); + homeProj.setOrgId(org.getId()); + homeProj.setIsArchived(org.isArchived()); + homeProj.setName((!org.getName().isEmpty() ? org.getName() : org.getId()) + " Home"); + + ProjectService ps = getProjectService(homeProj); + ProjectJson savedProj = ps.create(homeProj); + permissionService.initProjectPerms(homeProj.getId(), true, org.getCreator()); + return savedProj; + } + + private ProjectService getProjectService(ProjectJson json) { + String type = json.getProjectType(); + if (type == null || type.isEmpty()) { + try { + type = this.getProjectType(json.getProjectId()); + } catch (NotFoundException e) { + type = CrudConstants.DEFAULT; + } + json.setProjectType(type); + } + return serviceFactory.getProjectService(type); + } } diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java index dd8f3f76e..039b82d83 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java @@ -45,7 +45,7 @@ public void setProjectSchemas(ProjectSchemas projectSchemas) { } @GetMapping - public ProjectsResponse getAllProjects(Authentication auth, @RequestParam(required = false) String orgId, @RequestParam(required = false, defaultValue = Constants.FALSE) boolean includeArchived) { + public ProjectsResponse getAllProjects(Authentication auth, @RequestParam(required = false) String orgId, @RequestParam(required = false, defaultValue = Constants.FALSE) boolean includeArchived, @RequestParam(required = false, defaultValue = Constants.FALSE) boolean includeHomes) { ProjectsResponse response = new ProjectsResponse(); Collection allProjects = @@ -54,7 +54,7 @@ public ProjectsResponse getAllProjects(Authentication auth, @RequestParam(requir try { if (mss.hasProjectPrivilege(auth, projectJson.getProjectId(), Privileges.PROJECT_READ.name(), true) && projectJson.getDocId() != null - && (!Constants.TRUE.equals(projectJson.getIsArchived()) || includeArchived)) { + && (!projectJson.isArchived() || includeArchived) && (!projectJson.getId().endsWith(Constants.HOME_SUFFIX) || includeHomes)) { response.getProjects().add(projectJson); } } catch(NotFoundException ex) { diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java b/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java index 9d266c99b..87a536e2e 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java @@ -22,7 +22,7 @@ public class DefaultNodeUpdateFilter implements NodeUpdateFilter { @Override public boolean filterUpdate(NodeChangeInfo info, ElementJson updated, ElementJson existing) { if (!info.getOverwrite()) { - if (Constants.TRUE.equals(existing.getIsArchived()) || isUpdated(updated, existing, info)) { + if ((existing.isArchived() != null && existing.isArchived()) || isUpdated(updated, existing, info)) { return diffUpdateJson(updated, existing, info); } else { return false; diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/NodeChangeDomain.java b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeChangeDomain.java index f67329e26..c9799b3bb 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/domain/NodeChangeDomain.java +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeChangeDomain.java @@ -56,6 +56,7 @@ public void processElementAdded(NodeChangeInfo info, ElementJson element) { element.setCreator(commitJson.getCreator()); //Only set on creation of new element element.setCreated(commitJson.getCreated()); + element.setIsArchived(false); } public boolean processElementUpdated(NodeChangeInfo info, ElementJson element, ElementJson existing) { diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultNodeService.java b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultNodeService.java index 671581132..cb4b7c2d0 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultNodeService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultNodeService.java @@ -59,7 +59,7 @@ public void setEventPublisher(Collection eventPublisher) { @Override public void readAsStream(String projectId, String refId, Map params, OutputStream stream, String accept) throws IOException { - + Boolean deleted = Boolean.parseBoolean(params.getOrDefault(CrudConstants.DELETED, "false")); String commitId = params.getOrDefault(CrudConstants.COMMITID, null); if (commitId != null && !commitId.isEmpty()) { @@ -81,7 +81,7 @@ public void readAsStream(String projectId, String refId, Map par separator = ","; } - nodePersistence.streamAllAtCommit(projectId, refId, commitId, stream, separator); + nodePersistence.streamAllAtCommit(projectId, refId, commitId, stream, separator, deleted); if (!"application/x-ndjson".equals(accept)) { stream.write("]}".getBytes(StandardCharsets.UTF_8)); @@ -99,6 +99,7 @@ public ElementsResponse read(String projectId, String refId, String id, ElementsRequest req = buildRequest(id); return read(projectId, refId, req, params); } + Boolean deleted = Boolean.parseBoolean(params.getOrDefault(CrudConstants.DELETED, "false")); String commitId = params.getOrDefault(CrudConstants.COMMITID, null); if (commitId == null) { Optional commitJson = commitPersistence.findLatestByProjectAndRef(projectId, refId); @@ -111,7 +112,7 @@ public ElementsResponse read(String projectId, String refId, String id, ElementsResponse response = new ElementsResponse(); logger.debug("No ElementId given"); - List nodes = nodePersistence.findAll(projectId, refId, commitId); + List nodes = nodePersistence.findAll(projectId, refId, commitId, deleted); response.getElements().addAll(nodes); response.getElements().forEach(v -> v.setRefId(refId)); response.setCommitId(commitId); diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java index 82696c3fb..1d408f2ea 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java @@ -63,7 +63,7 @@ public ProjectJson create(ProjectJson project) { try { //TODO Transaction start ProjectJson savedProjectJson = projectPersistence.save(project); - savedProjectJson.setIsArchived("false"); + savedProjectJson.setIsArchived(false); //create and save master branch. We're combining operations with the branch unifiedDAO. branchPersistence.save(createMasterRefJson(savedProjectJson)); //TODO Transaction commit @@ -112,7 +112,7 @@ public RefJson createMasterRefJson(ProjectJson project) { branchJson.setCreated(project.getCreated()); branchJson.setProjectId(project.getId()); branchJson.setCreator(project.getCreator()); - branchJson.setIsArchived("false"); + branchJson.setIsArchived(false); return branchJson; } } diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/OrgDeleteService.java b/crud/src/main/java/org/openmbee/mms/crud/services/OrgDeleteService.java index 026032581..27f0a5b69 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/OrgDeleteService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/OrgDeleteService.java @@ -73,7 +73,7 @@ public OrganizationsResponse deleteOrg(String orgId, boolean hard) { projectDeleteService.deleteProject(proj.getId(), false); } orgPersistence.archiveById(orgId); - orgJson.setIsArchived(Constants.TRUE); + orgJson.setIsArchived(true); } diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java b/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java index b8488eb43..eceb61c42 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java @@ -54,7 +54,7 @@ public ProjectsResponse deleteProject(String projectId, boolean hard) { projectJson.setDeleted(true); } else { projectPersistence.archiveById(projectId); - projectJson.setIsArchived(Constants.TRUE); + projectJson.setIsArchived(true); } diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java b/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java index 29751bc26..626dc50e2 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java @@ -38,7 +38,7 @@ public void create(String projectId) { ProjectJson projectJson = newInstance(); projectJson.setProjectId(projectId); projectJson.setType("default"); - projectJson.setIsArchived("false"); + projectJson.setIsArchived(true); create(projectJson); } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java index 6426311ef..126450ea2 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java @@ -60,7 +60,7 @@ public RefJson save(RefJson refJson) { scopedBranch.setTimestamp(Formats.FORMATTER.parse(refJson.getCreated(), Instant::from)); scopedBranch.setParentRefId(refJson.getParentRefId()); scopedBranch.setDocId(refJson.getDocId()); - scopedBranch.setDeleted(Boolean.parseBoolean(refJson.getIsArchived())); + scopedBranch.setDeleted(refJson.isArchived()); //Setup global Branch object Optional project = projectDAO.findByProjectId(refJson.getProjectId()); @@ -104,8 +104,8 @@ public RefJson update(RefJson refJson) { ContextHolder.setContext(refJson.getProjectId()); Optional optionalExisting = branchDAO.findByBranchId(refJson.getId()); Branch existing = optionalExisting.get(); - if (refJson.getIsArchived() != null) { - existing.setDeleted(Boolean.parseBoolean(refJson.getIsArchived())); + if (refJson.isArchived() != null) { + existing.setDeleted(refJson.isArchived()); } branchDAO.save(existing); diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java index 44efd5f08..4ff500a34 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java @@ -110,14 +110,14 @@ public NodeGetInfo findById(String projectId, String refId, String commitId, Str } @Override - public List findAllByNodeType(String projectId, String refId, String commitId, int nodeType) { + public List findAllByNodeType(String projectId, String refId, String commitId, int nodeType, Boolean deleted) { ContextHolder.setContext(projectId, refId); String commitToPass = checkCommit(refId, commitId); List nodes; if (commitToPass != null) { nodes = nodeDAO.findAllByNodeType(nodeType); } else { - nodes = nodeDAO.findAllByDeletedAndNodeType(false, nodeType); + nodes = nodeDAO.findAllByDeletedAndNodeType(deleted, nodeType); } return new ArrayList<>(getNodeGetDomain().processGetJsonFromNodes(nodes, commitToPass).getActiveElementMap().values()); } @@ -130,28 +130,28 @@ public NodeGetInfo findAll(String projectId, String refId, String commitId, List } @Override - public List findAll(String projectId, String refId, String commitId) { + public List findAll(String projectId, String refId, String commitId, Boolean deleted) { ContextHolder.setContext(projectId, refId); List nodes; String commitToPass = checkCommit(refId, commitId); if (commitToPass != null) { nodes = nodeDAO.findAll(); } else { - nodes = nodeDAO.findAllByDeleted(false); + nodes = nodeDAO.findAllByDeleted(deleted); } return new ArrayList<>(getNodeGetDomain().processGetJsonFromNodes(nodes, commitToPass).getActiveElementMap().values()); } @Override - public void streamAllAtCommit(String projectId, String refId, String commitId, OutputStream stream, String separator) { + public void streamAllAtCommit(String projectId, String refId, String commitId, OutputStream stream, String separator, Boolean deleted) { ContextHolder.setContext(projectId, refId); List nodes; final String commitToPass = checkCommit(refId, commitId); if (commitToPass != null) { nodes = nodeDAO.findAll(); } else { - nodes = nodeDAO.findAllByDeleted(false); + nodes = nodeDAO.findAllByDeleted(deleted); } AtomicInteger counter = new AtomicInteger(); batches(nodes, streamLimit).forEach(ns -> { diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java index 65de4a4de..5ebda5f28 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java @@ -40,8 +40,8 @@ public OrgJson save(OrgJson orgJson) { Organization organization = organizationOptional.orElse(new Organization()); organization.setOrganizationId(orgJson.getId()); organization.setOrganizationName(orgJson.getName()); - if (orgJson.getIsArchived() != null) { - organization.setDeleted(Boolean.parseBoolean(orgJson.getIsArchived())); + if (orgJson.isArchived() != null) { + organization.setDeleted(orgJson.isArchived()); } return getOrgJson(orgDAO.save(organization)); } @@ -92,7 +92,7 @@ public boolean hasPublicPermissions(String orgId) { protected OrgJson getOrgJson(Organization organization) { OrgJson orgJson = new OrgJson(); orgJson.merge(jsonUtils.convertToMap(organization)); - orgJson.setIsArchived(String.valueOf(organization.isDeleted())); + orgJson.setIsArchived(organization.isDeleted()); orgJson.remove(OrgJson.DELETED); return orgJson; } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java index 927c6b3a3..4f12ef44c 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java @@ -65,7 +65,7 @@ public List findAllById(Set projectIds) { Project project = projectOption.get(); ContextHolder.setContext(project.getProjectId()); ProjectJson projectJson = projectIndexDAO.findById(project.getDocId()).orElse(null); - projectJson.setIsArchived(String.valueOf(project.isDeleted())); + projectJson.setIsArchived(project.isDeleted()); return projectJson; } return null; @@ -78,7 +78,7 @@ public List findAll() { ContextHolder.setContext(project.getProjectId()); ProjectJson projectJson = projectIndexDAO.findById(project.getDocId()).orElse(null); - projectJson.setIsArchived(String.valueOf(project.isDeleted())); + projectJson.setIsArchived(project.isDeleted()); return projectJson; }).filter(Objects::nonNull).collect(Collectors.toList()); } @@ -96,7 +96,7 @@ public Collection findAllByOrgId(String orgId) { return org.get().getProjects().stream().map(project -> { ContextHolder.setContext(project.getProjectId()); ProjectJson projectJson = projectIndexDAO.findById(project.getDocId()).orElse(null); - projectJson.setIsArchived(String.valueOf(project.isDeleted())); + projectJson.setIsArchived(project.isDeleted()); return projectJson; }).filter(Objects::nonNull).collect(Collectors.toList()); } @@ -159,7 +159,7 @@ public void archiveById(String projectId) { projectDAO.save(p); ProjectJson projectJson = projectJsonOption.get(); - projectJson.setIsArchived(Constants.TRUE); + projectJson.setIsArchived(true); ContextHolder.setContext(projectId); projectIndexDAO.update(projectJson); @@ -184,7 +184,7 @@ public ProjectJson save(ProjectJson projectJson) { proj.setOrganization(org.get()); proj.setProjectType(projectJson.getProjectType()); proj.setDocId(projectJson.getDocId()); - proj.setDeleted(Boolean.parseBoolean(projectJson.getIsArchived())); + proj.setDeleted(projectJson.isArchived()); try { projectDAO.save(proj); @@ -219,14 +219,14 @@ public ProjectJson update(ProjectJson projectJson) { throw new BadRequestException("Invalid organization"); } } - if (projectJson.getIsArchived() != null && !projectJson.getIsArchived().isEmpty()) { - proj.setDeleted(Boolean.parseBoolean(projectJson.getIsArchived())); + if (projectJson.isArchived() != null) { + proj.setDeleted(projectJson.isArchived()); } projectJson.setDocId(proj.getDocId()); projectDAO.save(proj); - projectJson.setIsArchived(String.valueOf(proj.isDeleted())); + projectJson.setIsArchived(proj.isDeleted()); ContextHolder.setContext(projectJson.getProjectId()); return projectIndexDAO.update(projectJson); } diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java index c5f368d5a..8435aee86 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java @@ -80,7 +80,7 @@ public void processElementAdded(NodeChangeInfo info, ElementJson element) { Node node = new Node(); node.setNodeId(element.getId()); - + ((FederatedNodeChangeInfo) info).getExistingNodeMap().put(element.getId(), node); super.processElementAdded(info, element); @@ -107,7 +107,9 @@ public boolean processElementUpdated(NodeChangeInfo info, ElementJson element, E previousDocId = n.getDocId(); ((FederatedNodeChangeInfo) info).getOldDocIds().add(previousDocId); if(n.isDeleted()) { - existing.setIsArchived(Constants.TRUE); + existing.setIsArchived(true); + } else { + existing.setIsArchived(false); } } else { return false; diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeGetDomain.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeGetDomain.java index 36489e1d3..8d437dfd4 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeGetDomain.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeGetDomain.java @@ -6,6 +6,7 @@ import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.domain.NodeGetDomain; import org.openmbee.mms.data.domains.scoped.Branch; import org.openmbee.mms.data.domains.scoped.Commit; @@ -119,9 +120,11 @@ protected NodeGetInfo processLatest(NodeGetInfo info) { continue; } if (federatedInfo.getExistingNodeMap().get(nodeId).isDeleted()) { + indexElement.setIsArchived(true); rejectDeleted(info, nodeId, indexElement); continue; } + indexElement.setIsArchived(false); info.getActiveElementMap().put(nodeId, indexElement); } return info; @@ -171,12 +174,15 @@ protected NodeGetInfo processCommit(NodeGetInfo info, String commitId) { Instant created = Instant.from(Formats.FORMATTER.parse(indexElement.getCreated())); if (commitId.equals(indexElement.getCommitId())) { //exact match + indexElement.setIsArchived(false); addActiveElement(info, nodeId, indexElement); } else if (created.isAfter(time)) { // element created after commit rejectNotFound(info, nodeId); } else if (modified.isAfter(time)) { // latest element is after commit Optional tryExact = nodeIndex.getByCommitId(commitId, nodeId); if (tryExact.isPresent()) { + ElementJson tryJson = tryExact.get(); + tryJson.setIsArchived(tryJson.get(CrudConstants.DELETED) == null ? false : (Boolean) tryJson.get(CrudConstants.DELETED)); addActiveElement(info, nodeId, tryExact.get()); continue; // found exact match at commit } @@ -187,10 +193,13 @@ protected NodeGetInfo processCommit(NodeGetInfo info, String commitId) { Formats.FORMATTER.format(time), refCommitIds); if (e.isPresent()) { // found version of element at commit time Instant realModified = Instant.from(Formats.FORMATTER.parse(e.get().getModified())); + ElementJson elementJson = e.get(); if (elementDeleted(nodeId, commitId, time, realModified, refCommitIds)) { - rejectDeleted(info, nodeId, e.get()); + elementJson.setIsArchived(true); + rejectDeleted(info, nodeId, elementJson); } else { - addActiveElement(info, nodeId, e.get()); + elementJson.setIsArchived(false); + addActiveElement(info, nodeId, elementJson); } } else { rejectNotFound(info, nodeId); // element not found at commit time @@ -200,11 +209,14 @@ protected NodeGetInfo processCommit(NodeGetInfo info, String commitId) { refCommitIds = getRefCommitIds(time); } if (elementDeleted(nodeId, commitId, time, modified, refCommitIds)) { + indexElement.setIsArchived(true); rejectDeleted(info, nodeId, indexElement); } else { + indexElement.setIsArchived(false); addActiveElement(info, nodeId, indexElement); } } else { // latest element version is version at commit, not deleted + indexElement.setIsArchived(false); addActiveElement(info, nodeId, indexElement); } } diff --git a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java index c29efa041..82f29e564 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java +++ b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java @@ -163,22 +163,17 @@ public void createLocalGroup(@PathVariable String group) { @PreAuthorize("isAuthenticated()") public GroupsResponse getAllGroups( Authentication auth) { - - GroupsResponse response = new GroupsResponse(); - Collection allGroups = groupPersistence.findAll(); - for (GroupJson group : allGroups) { - if (mss.hasGroupPrivilege(auth, group.getName(), Privileges.GROUP_READ.name(), false)) { - response.getGroups().add(group); - } - } - return response; + return new GroupsResponse(groupPersistence.findAll().stream().collect(Collectors.toList())); } @GetMapping(value = "/{group}") - @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_READ', true)") - public GroupResponse getGroup(@PathVariable String group) { - return new GroupResponse(groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)), - userGroupsPersistence.findUsersInGroup(group).stream().map(UserJson::getUsername).collect(Collectors.toList())); + @PreAuthorize("isAuthenticated()") + public GroupsResponse getUser(@PathVariable String group) { + GroupsResponse res = new GroupsResponse(); + List groups = new ArrayList<>(); + groups.add(groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND))); + res.setGroups(groups); + return res; } @DeleteMapping("/{group}") @@ -194,6 +189,13 @@ public void deleteLocalGroup(@PathVariable String group) { } } + @GetMapping(value = "/{group}/users") + @PreAuthorize("isAuthenticated()") + public GroupResponse getGroupUsers(@PathVariable String group) { + return new GroupResponse(groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)), + userGroupsPersistence.findUsersInGroup(group).stream().map(UserJson::getUsername).collect(Collectors.toList())); + } + @PostMapping("/{group}/users") @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_EDIT', true)") public GroupUpdateResponse updateGroupUsers(@PathVariable String group, diff --git a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupUsersResponse.java b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupUsersResponse.java deleted file mode 100644 index 16d7f8ff8..000000000 --- a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupUsersResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.openmbee.mms.groups.objects; - -import io.swagger.v3.oas.annotations.media.Schema; -import org.openmbee.mms.core.objects.BaseResponse; -import org.openmbee.mms.json.GroupJson; - -import java.util.ArrayList; -import java.util.List; - -public class GroupUsersResponse extends BaseResponse { - - @Schema(required = true) - private List users; - - public GroupUsersResponse() { - this.users = new ArrayList<>(); - } - - public List getUsers() { - return users; - } - - public void setUsers(List users) { - this.users = users; - } -} diff --git a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsResponse.java b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsResponse.java index 80e5fc6f2..ec19aeca1 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsResponse.java +++ b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupsResponse.java @@ -15,6 +15,10 @@ public GroupsResponse() { this.groups = new ArrayList<>(); } + public GroupsResponse(List groups) { + this.groups = groups; + } + public List getGroups() { return this.groups; } diff --git a/json/src/main/java/org/openmbee/mms/json/BaseJson.java b/json/src/main/java/org/openmbee/mms/json/BaseJson.java index 3cf194d38..06261f8f7 100644 --- a/json/src/main/java/org/openmbee/mms/json/BaseJson.java +++ b/json/src/main/java/org/openmbee/mms/json/BaseJson.java @@ -22,7 +22,7 @@ public class BaseJson extends HashMap { public static final String CREATED = "_created"; public static final String COMMITID = "_commitId"; public static final String TYPE = "type"; - public static final String IS_ARCHIVED = "_archived"; + public static final String IS_ARCHIVED = "archived"; public String getId() { return (String) this.get(ID); @@ -159,13 +159,13 @@ public T setCommitId(String commitId) { } @JsonProperty(IS_ARCHIVED) - public String getIsArchived() { - return (String) this.get(IS_ARCHIVED) == null ? null : String.valueOf(this.get(IS_ARCHIVED)); + public Boolean isArchived() { + return this.get(IS_ARCHIVED) == null ? null : (Boolean) this.get(IS_ARCHIVED); } @SuppressWarnings("unchecked") @JsonProperty(IS_ARCHIVED) - public T setIsArchived(String archived) { + public T setIsArchived(Boolean archived) { this.put(IS_ARCHIVED, archived); return (T) this; } diff --git a/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterNodeService.java b/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterNodeService.java index cfcc1ed8f..33761853b 100644 --- a/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterNodeService.java +++ b/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterNodeService.java @@ -9,6 +9,7 @@ import org.openmbee.mms.core.objects.ElementsResponse; import org.openmbee.mms.core.objects.Rejection; import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.domain.JsonDomain; import org.openmbee.mms.json.ElementJson; import org.openmbee.mms.crud.services.DefaultNodeService; @@ -31,10 +32,11 @@ public void setJupyterHelper(JupyterHelper jupyterHelper) { } public ElementsResponse readNotebooks(String projectId, String refId, String elementId, Map params) { + Boolean deleted = Boolean.parseBoolean(params.getOrDefault(CrudConstants.DELETED, "false")); ElementsRequest req = new ElementsRequest(); List reqs = new ArrayList<>(); if (elementId == null || elementId.isEmpty()) { - reqs.addAll(getNodePersistence().findAllByNodeType(projectId, refId, null,JupyterNodeType.NOTEBOOK.getValue())); + reqs.addAll(getNodePersistence().findAllByNodeType(projectId, refId, null,JupyterNodeType.NOTEBOOK.getValue(), deleted)); } else { reqs.add((new ElementJson()).setId(elementId)); } diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaNodeService.java b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaNodeService.java index aeb2828d6..13240e06b 100644 --- a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaNodeService.java +++ b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaNodeService.java @@ -83,7 +83,7 @@ public MountJson getProjectUsages(String projectId, String refId, String commitI boolean restrictOnPermissions) { ContextHolder.setContext(projectId, refId); saw.add(Pair.of(projectId, refId)); - List mountsJson = nodePersistence.findAllByNodeType(projectId,refId,commitId,MsosaNodeType.PROJECTUSAGE.getValue()); + List mountsJson = nodePersistence.findAllByNodeType(projectId,refId,commitId,MsosaNodeType.PROJECTUSAGE.getValue(), false); Authentication auth = SecurityContextHolder.getContext().getAuthentication(); List mountValues = new ArrayList<>(); diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaViewService.java b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaViewService.java index cf1e7a4da..30be89c72 100644 --- a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaViewService.java +++ b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaViewService.java @@ -11,6 +11,7 @@ import java.util.Set; import java.util.UUID; +import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.domain.JsonDomain; import org.openmbee.mms.msosa.MsosaConstants; import org.openmbee.mms.msosa.MsosaNodeType; @@ -31,7 +32,8 @@ public class MsosaViewService extends MsosaNodeService implements ViewService { public ElementsResponse getDocuments(String projectId, String refId, Map params) { ContextHolder.setContext(projectId, refId); String commitId = params.getOrDefault(MsosaConstants.COMMITID, null); - List documents = getNodePersistence().findAllByNodeType(projectId, refId, commitId, MsosaNodeType.DOCUMENT.getValue()); + Boolean deleted = Boolean.parseBoolean(params.getOrDefault(CrudConstants.DELETED, "false")); + List documents = getNodePersistence().findAllByNodeType(projectId, refId, commitId, MsosaNodeType.DOCUMENT.getValue(), deleted); for (ElementJson e: documents) { Optional parent = getFirstRelationshipOfType(projectId, refId, commitId, e, Arrays.asList(MsosaNodeType.GROUP.getValue()), MsosaConstants.OWNERID); @@ -81,9 +83,10 @@ public void addChildViews(ElementsResponse res, Map params) { @Override public ElementsResponse getGroups(String projectId, String refId, Map params) { ContextHolder.setContext(projectId, refId); + Boolean deleted = Boolean.parseBoolean(params.getOrDefault(CrudConstants.DELETED, "false")); String commitId = params.getOrDefault(MsosaConstants.COMMITID, null); List groups = getNodePersistence().findAllByNodeType(projectId, refId, commitId, - MsosaNodeType.GROUP.getValue()); + MsosaNodeType.GROUP.getValue(), deleted); ElementsResponse res = this.read(projectId, refId, buildRequestFromJsons(groups), params); for (ElementJson e: res.getElements()) { Optional parent = getFirstRelationshipOfType(projectId, refId, commitId, e, diff --git a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java index da399404b..96507e0ef 100644 --- a/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java +++ b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java @@ -72,7 +72,7 @@ public UsersResponse getUsers() { return res; } - @GetMapping(value = "/users/:username") + @GetMapping(value = "/users/{username}") @PreAuthorize("isAuthenticated()") public UsersResponse getUser(@PathVariable String username) { UsersResponse res = new UsersResponse(); @@ -83,7 +83,7 @@ public UsersResponse getUser(@PathVariable String username) { return res; } - @GetMapping(value = "/users/:username/groups") + @GetMapping(value = "/users/{username}/groups") @PreAuthorize("isAuthenticated()") public UserGroupsResponse getUserGroups(@PathVariable String username) { return new UserGroupsResponse(usersDetailsService.loadUserByUsername(username).getUser(), userGroupsPersistence.findGroupsAssignedToUser(username).stream().map(GroupJson::getName).collect(Collectors.toList())); diff --git a/users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java b/users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java index f5c387ade..ec3c8c74b 100644 --- a/users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java +++ b/users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java @@ -15,10 +15,14 @@ public class UserGroupsResponse { @Schema(nullable = true) private Collection groups; + @Schema(defaultValue = "false") + private Boolean admin; + public UserGroupsResponse(){} public UserGroupsResponse(UserJson user, Collection groups){ - this.user = user.getName(); + this.user = user.getUsername(); + this.admin = user.isAdmin(); this.groups = groups; } @@ -37,4 +41,12 @@ public Collection getGroups() { public void setGroups(Collection groups) { this.groups = groups; } + + public Boolean isAdmin() { + return this.admin; + } + + public void setAdmin(Boolean admin) { + this.admin = admin; + } } From 0ecd320b48e3a414bfd86e5b12cf5cb7274d3ef7 Mon Sep 17 00:00:00 2001 From: Enquier Date: Tue, 28 May 2024 11:12:20 -0600 Subject: [PATCH 16/19] fix conflict with mdk --- json/src/main/java/org/openmbee/mms/json/BaseJson.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json/src/main/java/org/openmbee/mms/json/BaseJson.java b/json/src/main/java/org/openmbee/mms/json/BaseJson.java index 06261f8f7..31a76d2e5 100644 --- a/json/src/main/java/org/openmbee/mms/json/BaseJson.java +++ b/json/src/main/java/org/openmbee/mms/json/BaseJson.java @@ -22,7 +22,7 @@ public class BaseJson extends HashMap { public static final String CREATED = "_created"; public static final String COMMITID = "_commitId"; public static final String TYPE = "type"; - public static final String IS_ARCHIVED = "archived"; + public static final String IS_ARCHIVED = "_archived"; public String getId() { return (String) this.get(ID); From ecbf9dced8ffc721e3e63ea35a76af673a54dbbf Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 29 May 2024 10:47:36 -0600 Subject: [PATCH 17/19] fix npe --- .../dao/FederatedProjectPersistence.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java index 4f12ef44c..fea5c2ddb 100644 --- a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java @@ -184,7 +184,10 @@ public ProjectJson save(ProjectJson projectJson) { proj.setOrganization(org.get()); proj.setProjectType(projectJson.getProjectType()); proj.setDocId(projectJson.getDocId()); - proj.setDeleted(projectJson.isArchived()); + + if (projectJson.isArchived() != null) { + proj.setDeleted(projectJson.isArchived()); + } try { projectDAO.save(proj); From f1c4e8fb19cbc981f513561c198bf237832d97d6 Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 3 Jul 2024 06:01:44 -0600 Subject: [PATCH 18/19] group perms fixes --- .../elastic/config/ElasticsearchConfig.java | 58 ++++++++-- .../resources/application.properties.example | 6 +- .../resources/application-example.properties | 106 ++++++++++++++++++ .../src/main/resources/application.properties | 1 + .../security/LdapUsersDetailsService.java | 13 ++- .../mms/users/objects/UserCreateRequest.java | 4 - .../security/DefaultUsersDetailsService.java | 6 +- 7 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 example/src/main/resources/application-example.properties create mode 100644 example/src/main/resources/application.properties diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/config/ElasticsearchConfig.java b/elastic/src/main/java/org/openmbee/mms/elastic/config/ElasticsearchConfig.java index be2380345..c89a22ed4 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/config/ElasticsearchConfig.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/config/ElasticsearchConfig.java @@ -1,15 +1,27 @@ package org.openmbee.mms.elastic.config; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.security.KeyStore; + +import javax.net.ssl.SSLContext; + import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLContexts; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback; import org.elasticsearch.client.RestHighLevelClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,7 +33,7 @@ public class ElasticsearchConfig { private String elasticsearchHost; @Value("${elasticsearch.port}") private int elasticsearchPort; - @Value("${elasticsearch.http}") + @Value("${elasticsearch.http:http}") private String elasticsearchHttp; @Value("${elasticsearch.password:#{null}}") @@ -29,28 +41,50 @@ public class ElasticsearchConfig { @Value("${elasticsearch.username:#{null}}") private String elasticsearchUsername; - @Bean(name = "clientElastic", destroyMethod = "close") - public RestHighLevelClient restClient() { + @Value("${elasticsearch.truststore:#{null}}") + private String elasticsearchTruststore; + @Value("${elasticsearch.storepass:#{null}}") + private String elasticStorepass; + private static Logger logger = LoggerFactory.getLogger(ElasticsearchConfig.class); + + @Bean(name = "clientElastic", destroyMethod = "close") + public RestHighLevelClient restClient() { RestClientBuilder builder = RestClient.builder(new HttpHost(elasticsearchHost, elasticsearchPort, elasticsearchHttp)); builder.setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(10000).setSocketTimeout(1000000)); + + - if (elasticsearchPassword != null && elasticsearchUsername != null && !elasticsearchPassword.isEmpty() && !elasticsearchUsername.isEmpty()) { - final CredentialsProvider credentialsProvider = - new BasicCredentialsProvider(); - credentialsProvider.setCredentials(AuthScope.ANY, - new UsernamePasswordCredentials(elasticsearchUsername, elasticsearchPassword)); - builder.setHttpClientConfigCallback(new HttpClientConfigCallback() { + builder.setHttpClientConfigCallback(new HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient( HttpAsyncClientBuilder httpClientBuilder) { - return httpClientBuilder - .setDefaultCredentialsProvider(credentialsProvider); + if (elasticsearchPassword != null && elasticsearchUsername != null && !elasticsearchPassword.isEmpty() && !elasticsearchUsername.isEmpty()) { + final CredentialsProvider credentialsProvider =new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, + new UsernamePasswordCredentials(elasticsearchUsername, elasticsearchPassword)); + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + if (elasticsearchHttp != null && elasticsearchHttp == "https" && elasticsearchTruststore != null && elasticStorepass != null) { + try { + // SSLFactory sslFactory = SSLFactory.builder() + // .withDefaultTrustMaterial() // JDK trusted CA's + // .withSystemTrustMaterial() // OS trusted CA's + // .withTrustMaterial(trustStorePath, password) + // .build(); + // SSLContextBuilder sslBuilder = SSLContexts.custom().loadTrustMaterial(new File(elasticsearchTruststore), elasticStorepass.toCharArray()); + // SSLContext sslContext = sslBuilder.build(); + // httpClientBuilder.setSSLContext(sslContext); + } catch (Exception e ){ + logger.debug("Error unable to load ssl truststore: " + e.getMessage()); + return httpClientBuilder; + } + } + return httpClientBuilder; } }); - } RestHighLevelClient client = new RestHighLevelClient(builder); diff --git a/elastic/src/main/resources/application.properties.example b/elastic/src/main/resources/application.properties.example index 0725b51f5..f7a196931 100644 --- a/elastic/src/main/resources/application.properties.example +++ b/elastic/src/main/resources/application.properties.example @@ -9,4 +9,8 @@ elasticsearch.limit.index=5000 #Optional Elasticsearch Basic Authentication Credentials elasticsearch.username= -elasticsearch.password= \ No newline at end of file +elasticsearch.password= + +#Optional Elasticsearch Truststore Configuration +elasticsearch.truststore= +elasticsearch.storepass= \ No newline at end of file diff --git a/example/src/main/resources/application-example.properties b/example/src/main/resources/application-example.properties new file mode 100644 index 000000000..a9e4e420f --- /dev/null +++ b/example/src/main/resources/application-example.properties @@ -0,0 +1,106 @@ +# See authenticator module for example configuration +mms.admin.username=test +mms.admin.password=test + +mms.stream.batch.size=100000 +mms.optimize-for-federated=true + +#Comma Separated list of allowed cross site origins +cors.allowed.origins=* + +jwt.secret=make_me_something_really_long +jwt.expiration=86400 +jwt.header=Authorization + +# See ldap module for example configuration +ldap.enabled=false +ldap.ad.enabled=false +ldap.provider.base=dc=directory,dc=openmbee,dc=org +ldap.provider.url=ldaps://ldap.openmbee.org +ldap.provider.userdn= +ldap.provider.password= +ldap.user.dn.pattern=uid={0},ou=personnel +ldap.user.attributes.username= +ldap.user.attributes.email= +ldap.user.attributes.firstname= +ldap.user.attributes.lastname= +ldap.user.attributes.update=24 +ldap.group.role.attribute=cn +ldap.group.search.base=ou=groups +ldap.group.search.filter=uniqueMember={0} + +# See core module for example configuration +spring.datasource.url=jdbc:postgresql://localhost:5432 +#spring.datasource.url=jdbc:mysql://localhost:3306 +spring.datasource.database=mms +spring.datasource.username=mmsuser +spring.datasource.password=test1234 +spring.datasource.driver-class-name=org.postgresql.Driver +#spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.initialization-mode=always + +# The SQL dialect makes Hibernate generate better SQL for the chosen database +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect +#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect +#spring.jpa.properties.hibernate.dialect.storage_engine=innodb + +# Hibernate ddl auto (create, create-drop, validate, update) +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.jpa.open-in-view=false + +spring.main.allow-bean-definition-overriding=true +spring.main.allow-circular-references=true +spring.mvc.pathmatch.matching-strategy=ant_path_matcher + +#Configuration for Elasticsearch +elasticsearch.host=localhost +elasticsearch.port=9200 +elasticsearch.http=http +elasticsearch.index.element=mms +elasticsearch.limit.insert=80 +elasticsearch.limit.result=10000 +elasticsearch.limit.term=1000 +elasticsearch.limit.scrollTimeout=1000 +elasticsearch.limit.get=100000 +elasticsearch.limit.index=5000 +elasticsearch.limit.commit=100000 + +#optional Elasticsearch Basic Authentication Credentials +elasticsearch.username= +elasticsearch.password= + +#Optional Elasticsearch Truststore Configuration +elasticsearch.truststore= +elasticsearch.storepass= + +#Configuration for TWC +#port is for REST interface +#aliases are for clustered usages +twc.instances[0].url=dev-twc-03.domain.com +twc.instances[0].protocol=https +twc.instances[0].port=8111 +twc.instances[0].aliases[0]=dev-twc-02.domain.com +twc.instances[0].aliases[1]=dev-twc-01.domain.com + +springdoc.swagger-ui.path=/v3/swagger-ui.html +#For sorting endpoints alphabetically +springdoc.swagger-ui.operationsSorter=alpha +#For sorting tags alphabetically +springdoc.swagger-ui.tagsSorter=alpha +springdoc.default-produces-media-type=application/json +springdoc.swagger-ui.displayOperationId=true + +storage.provider=s3 +s3.endpoint=http://localhost:9000 +s3.access_key=admintest +s3.secret_key=admintest +#s3.region=optional +#s3.bucket=optional + +## Use Azure Blob Storage +#storage.provider=azureBlob +#spring.cloud.azure.storage.blob.account-name=test +#spring.cloud.azure.storage.blob.account-key=test +#spring.cloud.azure.storage.blob.endpoint=test +#spring.cloud.azure.storage.blob.container-name=test-container \ No newline at end of file diff --git a/example/src/main/resources/application.properties b/example/src/main/resources/application.properties new file mode 100644 index 000000000..45ac32f15 --- /dev/null +++ b/example/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.active.profile=example \ No newline at end of file diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java index 85c9190e8..c6f34f242 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java @@ -107,7 +107,7 @@ public void changeUserPassword(String username, String password, boolean asAdmin } UserJson user = userOptional.get(); - if (user.getType() == LocalUsersDetailsService.TYPE) { + if (user.getPassword() != null || !user.getPassword().isBlank()) { super.changeUserPassword(username, password, asAdmin); } else { throw new BadRequestException("Unable to change passwords for non-local users"); @@ -157,7 +157,7 @@ public DefaultUsersDetails loadUserByUsername(String username) throws UsernameNo userJson = saveUser(getUser(username)); } else { userJson = user.get(); - if (user.get().getType().equals(LdapUsersDetailsService.TYPE) && userJson.getModified() != null && Instant.parse(userJson.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { + if ((userJson.getPassword() == null || userJson.getPassword().isBlank()) && userJson.getModified() != null && Instant.parse(userJson.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { userJson = saveUser(getUser(username)); } } @@ -184,7 +184,6 @@ private UserJson create(DirContextOperations userData) { user.setUsername(username); user.setEnabled(true); user.setAdmin(false); - user.setType(LdapUsersDetailsService.TYPE); user.setPassword(null); return update(userData, user); } @@ -204,7 +203,13 @@ public Collection getUserAuthorities(String username HardcodedFilter groupsFilter = new HardcodedFilter( "(" + groupSearchFilter.replace("{0}", LdapEncoder.filterEncode(userDn)) + ")"); andFilter.and(groupsFilter); - andFilter.and(orFilter); + if (!orFilter.encode().isEmpty()) { + logger.debug("LDAP Filter Query: " + orFilter.encode()); + andFilter.and(orFilter); + } else { + logger.debug("Empty Filter"); + } + String filter = andFilter.encode(); Set allGroups = ldapTemplate .searchForSingleAttributeValues(groupSearchBase, orFilter.encode(), new Object[]{""}, groupRoleAttribute); diff --git a/users/src/main/java/org/openmbee/mms/users/objects/UserCreateRequest.java b/users/src/main/java/org/openmbee/mms/users/objects/UserCreateRequest.java index 921f0b5e1..f3e952ea5 100644 --- a/users/src/main/java/org/openmbee/mms/users/objects/UserCreateRequest.java +++ b/users/src/main/java/org/openmbee/mms/users/objects/UserCreateRequest.java @@ -63,10 +63,6 @@ public void setAdmin(boolean admin) { this.admin = admin; } - public String getType() { - return this.type; - } - public void setType(String type) { this.type = type; } diff --git a/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java b/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java index be83d4b5b..ace902e3b 100644 --- a/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java +++ b/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java @@ -99,10 +99,8 @@ public UserJson update(UserCreateRequest req, UserJson user) { ) { user.setLastName(req.getLastName()); } - if (req.isEnabled() != null && user.isEnabled() != req.isEnabled()) - - if (req.getType() != null) { - user.setType(req.getType()); + if (req.isEnabled() != null && user.isEnabled() != req.isEnabled()){ + user.setEnabled(req.isEnabled()); } return saveUser(user); } From e7f1a208613eba45eb7800c7fb46f92d1b5f880e Mon Sep 17 00:00:00 2001 From: Enquier Date: Wed, 3 Jul 2024 06:02:25 -0600 Subject: [PATCH 19/19] fix gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 661bad695..fae6c0b1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ .gradle **/build/ !gradle/wrapper/gradle-wrapper.jar -application.properties +application-*.properties +!application-example.properties +!application-test.properties localhost-env.json .vscode certs/