diff --git a/.gitignore b/.gitignore index d9defa701..fae6c0b1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,12 @@ .gradle **/build/ !gradle/wrapper/gradle-wrapper.jar -application.properties +application-*.properties +!application-example.properties +!application-test.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/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/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 c0749c4a6..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) { @@ -251,7 +254,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/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..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 @@ -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"; @@ -23,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-]+$"); @@ -40,9 +43,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/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/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/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/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/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..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 @@ -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,12 +17,14 @@ 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; 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; @@ -33,6 +36,8 @@ public class DefaultPermissionService implements PermissionService { private BranchPersistence branchPersistence; private ProjectPersistence projectPersistence; private OrgPersistence orgPersistence; + private GroupPersistence groupPersistence; + private PermissionsDelegateUtil permissionsDelegateUtil; @Autowired @@ -55,6 +60,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 +95,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 +116,7 @@ public PermissionUpdatesResponse updateOrgUserPerms(PermissionUpdateRequest req, responseBuilder.insert(recalculateInheritedPerms(project)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -113,7 +131,7 @@ public PermissionUpdatesResponse updateOrgGroupPerms(PermissionUpdateRequest req responseBuilder.insert(recalculateInheritedPerms(project)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -128,7 +146,7 @@ public PermissionUpdatesResponse updateProjectUserPerms(PermissionUpdateRequest responseBuilder.insert(recalculateInheritedPerms(b)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -143,7 +161,7 @@ public PermissionUpdatesResponse updateProjectGroupPerms(PermissionUpdateRequest responseBuilder.insert(recalculateInheritedPerms(b)); } - return responseBuilder.getPermissionUpdatesReponse(); + return responseBuilder.getPermissionUpdatesResponse(); } @Override @@ -163,6 +181,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 +210,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 +222,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 +241,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 +277,19 @@ 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) { + + 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); + } + @Override public boolean isProjectInherit(String projectId) { return projectPersistence.inheritsPermissions(projectId); @@ -250,6 +310,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 +367,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 +394,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 +421,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..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 @@ -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 group " + group.getName() + + " (" + group.getName() + ")"); + } } 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/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..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 @@ -6,19 +6,25 @@ 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; 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; +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 +35,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 +45,33 @@ public class OrgsController extends BaseController { OrgPersistence organizationRepository; + OrgDeleteService orgDeleteService; + + ProjectDeleteService projectDeleteService; + @Autowired public OrgsController(OrgPersistence organizationRepository) { this.organizationRepository = organizationRepository; } + @Autowired + public void setOrgDeleteService(OrgDeleteService orgDeleteService) { + this.orgDeleteService = orgDeleteService; + } + + @Autowired + public void setProjectDeleteService(ProjectDeleteService projectDeleteService) { + this.projectDeleteService = projectDeleteService; + } + @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) + && (!org.isArchived() || includeArchived)) { response.getOrgs().add(org); } } @@ -109,8 +131,28 @@ public OrganizationsResponse createOrUpdateOrgs( 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) { @@ -121,18 +163,55 @@ 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 (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")); + } + } } - OrgJson deleted = organizationRepository.deleteById(orgId); - response.setOrgs(List.of(deleted)); - return response; + 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 7e9a2ab92..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) { + 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.getIsDeleted())) { + && (!projectJson.isArchived() || includeArchived) && (!projectJson.getId().endsWith(Constants.HOME_SUFFIX) || includeHomes)) { 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..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.getIsDeleted()) || isUpdated(updated, existing, info)) { + if ((existing.isArchived() != null && existing.isArchived()) || 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/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 bfbca9148..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,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..27f0a5b69 --- /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(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..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 @@ -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(true); } - projectJson.setIsDeleted(Constants.TRUE); + res.add(projectJson); return response.setProjects(res); } 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..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 @@ -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,31 @@ public class Group extends Base { @ManyToMany(mappedBy = "groups") private Collection users; - public Group() {} + @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() { + 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; + this.typeString = VALID_GROUP_TYPES.REMOTE.toString().toLowerCase(); } public String getName() { @@ -31,6 +53,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 +84,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/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 e25bc5992..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 @@ -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,23 @@ 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; + + private String type; 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 +65,7 @@ public String getUsername() { public void setUsername(String username) { this.username = username; } - + public String getEmail() { return email; } @@ -133,7 +142,7 @@ public Collection getGroups() { return groups; } - public void setGroups(Collection groups) { + public void setGroups(Set groups) { this.groups = groups; } @@ -144,4 +153,13 @@ public boolean isAdmin() { 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/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/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java b/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java index 1d4a1d18f..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,6 +38,7 @@ public void create(String projectId) { ProjectJson projectJson = newInstance(); projectJson.setProjectId(projectId); projectJson.setType("default"); + projectJson.setIsArchived(true); create(projectJson); } 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/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-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-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/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/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/FederatedBranchPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java index bfc586066..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,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(refJson.isArchived()); //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.isArchived() != null) { + existing.setDeleted(refJson.isArchived()); + } + + branchDAO.save(existing); + return branchIndexDAO.update(refJson); } @Override 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..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 @@ -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; @@ -34,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); } @@ -61,4 +63,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/dao/FederatedNodePersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java index 446d8499c..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 -> { @@ -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/dao/FederatedOrgPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java index c293e6804..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 @@ -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.isArchived() != null) { + organization.setDeleted(orgJson.isArchived()); + } 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(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..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 @@ -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(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(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(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(true); ContextHolder.setContext(projectId); projectIndexDAO.update(projectJson); @@ -177,7 +184,10 @@ public ProjectJson save(ProjectJson projectJson) { proj.setOrganization(org.get()); proj.setProjectType(projectJson.getProjectType()); proj.setDocId(projectJson.getDocId()); - proj.setDeleted(Boolean.parseBoolean(projectJson.getIsDeleted())); + + if (projectJson.isArchived() != null) { + proj.setDeleted(projectJson.isArchived()); + } try { projectDAO.save(proj); @@ -188,7 +198,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 +222,14 @@ public ProjectJson update(ProjectJson projectJson) { throw new BadRequestException("Invalid organization"); } } + if (projectJson.isArchived() != null) { + proj.setDeleted(projectJson.isArchived()); + } projectJson.setDocId(proj.getDocId()); + + projectDAO.save(proj); + projectJson.setIsArchived(proj.isDeleted()); ContextHolder.setContext(projectJson.getProjectId()); return projectIndexDAO.update(projectJson); } 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/domain/FederatedNodeChangeDomain.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java index 1d4ecc866..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.setIsDeleted(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/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/DefaultFederatedPermissionsDelegateFactory.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java index 8a9cf14d6..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 @@ -8,11 +8,14 @@ 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; +import org.openmbee.mms.rdb.repositories.GroupRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -24,6 +27,7 @@ public class DefaultFederatedPermissionsDelegateFactory implements PermissionsDe private ProjectDAO projectDAO; private BranchGDAO branchDAO; private OrgDAO orgDAO; + private GroupRepository groupRepository; @Autowired public void setApplicationContext(ApplicationContext applicationContext) { @@ -45,6 +49,11 @@ public void setOrgDAO(OrgDAO orgDAO) { this.orgDAO = orgDAO; } + @Autowired + public void setGroupRepository(GroupRepository groupRepository) { + this.groupRepository = groupRepository; + } + @Override public PermissionsDelegate getPermissionsDelegate(ProjectJson project) { Optional projectOptional = projectDAO.findByProjectId(project.getProjectId()); @@ -74,4 +83,13 @@ public PermissionsDelegate getPermissionsDelegate(RefJson branch) { } return applicationContext.getBean(DefaultBranchPermissionsDelegate.class, branchOptional.get()); } + + @Override + public PermissionsDelegate getPermissionsDelegate(GroupJson group) { + Optional groupOptional = groupRepository.findByName(group.getName()); + if(groupOptional.isEmpty()) { + throw new NotFoundException("group not found"); + } + 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 new file mode 100644 index 000000000..97718971a --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultGroupPermissionsDelegate.java @@ -0,0 +1,302 @@ +package org.openmbee.mms.federatedpersistence.permissions; + +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.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; + 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."); + } + + if (groupPerms.contains(AuthorizationConstants.MMSADMIN)) { + return true; + } + + 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().findByUsernameIgnoreCase(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 an Org"); + } + return false; + } + + @Override + public PermissionResponse getInherit() { + //Orgs will not inherit + return PermissionResponse.getDefaultResponse(); + } + + @Override + public void setPublic(boolean isPublic) { + group.setPublic(isPublic); + getGroupRepo().save(group); + } + + @Override + public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest req) { + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); + + switch(req.getAction()) { + case MODIFY: + for (PermissionUpdateRequest.Permission p: req.getPermissions()) { + Optional user = getUserRepo().findByUsernameIgnoreCase(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().findByUsernameIgnoreCase(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().findByUsernameIgnoreCase(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) { + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); + + 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/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/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/gradlew b/gradlew old mode 100755 new mode 100644 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..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 @@ -1,35 +1,56 @@ 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.stream.Collectors; -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.exceptions.BadRequestException; -import org.openmbee.mms.core.exceptions.ConflictException; -import org.openmbee.mms.core.exceptions.NotFoundException; +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.UserJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; 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.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 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 +67,82 @@ 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; + } + + @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}") - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) + @PreAuthorize("isAuthenticated()") @ResponseBody public void createLocalGroup(@PathVariable String group) { GroupJson groupJson = groupPersistence.findByName(group).orElse(null); @@ -65,27 +160,26 @@ public void createLocalGroup(@PathVariable String group) { } @GetMapping - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) - public GroupsResponse getAllGroups() { - Collection groups = groupPersistence.findAll(); - GroupsResponse response = new GroupsResponse(); - response.setGroups(groups.stream().map(GroupJson::getName).collect(Collectors.toList())); - return response; + @PreAuthorize("isAuthenticated()") + public GroupsResponse getAllGroups( + Authentication auth) { + return new GroupsResponse(groupPersistence.findAll().stream().collect(Collectors.toList())); } - @GetMapping("/{group}") - @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) - 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())); + @GetMapping(value = "/{group}") + @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}") - @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)) { @@ -95,8 +189,15 @@ 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(AuthorizationConstants.IS_MMSADMIN) + @PreAuthorize("@mss.hasGroupPrivilege(authentication, #group, 'GROUP_EDIT', true)") public GroupUpdateResponse updateGroupUsers(@PathVariable String group, @RequestBody GroupUpdateRequest groupUpdateRequest) { @@ -139,4 +240,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/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..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 @@ -1,19 +1,30 @@ 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 { - @Schema(required = true) - private List groups; + private List groups; - public List getGroups() { - return groups; + public GroupsResponse() { + this.groups = new ArrayList<>(); } - public void setGroups(List groups) { + public GroupsResponse(List groups) { this.groups = groups; } + + public List getGroups() { + return this.groups; + } + + public void setGroups(List groups) { + this.groups = 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 b99b8b8e7..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_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 Boolean isArchived() { + return this.get(IS_ARCHIVED) == null ? null : (Boolean) 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(Boolean archived) { + this.put(IS_ARCHIVED, archived); return (T) this; } 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/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/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/ldap/ldap.gradle b/ldap/ldap.gradle index 588391d59..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 60% 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..b86c4e85d 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,8 @@ 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; import java.util.*; @@ -18,17 +20,24 @@ 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.authority.mapping.GrantedAuthoritiesMapper; +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; @@ -63,6 +72,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; @@ -95,7 +107,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; @@ -111,6 +124,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) @@ -141,7 +159,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 @@ -149,93 +167,44 @@ LdapAuthoritiesPopulator ldapAuthoritiesPopulator(@Qualifier("contextSource") Ba */ class CustomLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator { - final 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 = 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(); - 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())); - - 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(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); } } - 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.getPrincipal(), auth.getCredentials(), ldapUsersDetailsService.getUserAuthorities(((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<>(); @@ -250,7 +219,7 @@ public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() { provider.setSearchFilter(userSearchFilter); provider.setConvertSubErrorCodesToExceptions(true); provider.setUseAuthenticationRequestCredentials(true); - return provider; + return new CustomActiveDirectoryLdapAuthenticationProvider(provider); } @Bean @@ -270,33 +239,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..c6f34f242 --- /dev/null +++ b/ldap/src/main/java/org/openmbee/mms/ldap/security/LdapUsersDetailsService.java @@ -0,0 +1,365 @@ +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.getPassword() != null || !user.getPassword().isBlank()) { + 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 ((userJson.getPassword() == null || userJson.getPassword().isBlank()) && 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.setPassword(null); + return update(userData, user); + } + + public Collection getUserAuthorities(String username) { + Collection definedGroups = groupPersistence.findAll(); + OrFilter orFilter = new OrFilter(); + UserJson user = getUser(username); + for (GroupJson definedGroup : definedGroups) { + //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)) + ")"); + andFilter.and(groupsFilter); + 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); + 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<>(); + + //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())); + } 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/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..dabaeb2cf 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.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 UserDetailsServiceImpl 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(UserDetailsServiceImpl userDetailsService) { + public void setUserDetailsService(DefaultUsersDetailsService 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/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/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/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java b/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java new file mode 100644 index 000000000..08253ecd8 --- /dev/null +++ b/localauth/src/main/java/org/openmbee/mms/localauth/security/LocalUsersDetailsService.java @@ -0,0 +1,10 @@ +package org.openmbee.mms.localauth.security; + +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 +@Conditional(LocalAuthCondition.class) +public class LocalUsersDetailsService extends DefaultUsersDetailsService {} \ 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/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/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java index 9790620ca..140e9779b 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, #group, '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/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..410a320cc --- /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/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..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 @@ -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.equals(Constants.MASTER_BRANCH)) { break; } Optional parent = branchRepository.findByBranchId(currentRef); 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..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 @@ -3,32 +3,24 @@ 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.DefaultUsersDetailsService; +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 DefaultUsersDetailsService { @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + public UsersDetails loadUserByUsername(String username) throws UsernameNotFoundException { Optional userOptional = userPersistence.findByUsername(username); UserJson user; @@ -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, userGroupsPersistence.findGroupsAssignedToUser(username)); } public UserJson addUser(String username) { @@ -45,7 +37,40 @@ public UserJson addUser(String username) { user.setUsername(username); //TODO: fill in user details from TWC user.setEnabled(true); - return userPersistence.save(user); + + return register(user); + } + + 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"); + } + + 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 new file mode 100644 index 000000000..f7e36ffd0 --- /dev/null +++ b/twc/src/main/java/org/openmbee/mms/twc/utilities/AdminUtils.java @@ -0,0 +1,38 @@ +package org.openmbee.mms.twc.utilities; + +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.springframework.http.ResponseEntity; + + +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..96507e0ef --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/controller/UsersController.java @@ -0,0 +1,129 @@ +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.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; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.stream.Collectors; + +@RestController +@Tag(name = "Auth") +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; + } + + @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 = new ArrayList<>(); + 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() { + 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 70% 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..f3e952ea5 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,11 @@ public void setAdmin(boolean admin) { this.admin = admin; } + public void setType(String type) { + this.type = type; + } + + public Boolean isEnabled() { return enabled; } + + public void setEnabled(Boolean enabled) { this.enabled = enabled; } } 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..ec3c8c74b --- /dev/null +++ b/users/src/main/java/org/openmbee/mms/users/objects/UserGroupsResponse.java @@ -0,0 +1,52 @@ +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; + + @Schema(defaultValue = "false") + private Boolean admin; + + public UserGroupsResponse(){} + + public UserGroupsResponse(UserJson user, Collection groups){ + this.user = user.getUsername(); + this.admin = user.isAdmin(); + 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; + } + + public Boolean isAdmin() { + return this.admin; + } + + public void setAdmin(Boolean admin) { + this.admin = admin; + } +} 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/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/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/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java b/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java similarity index 64% rename from localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java rename to users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java index 893692b9a..ace902e3b 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java +++ b/users/src/main/java/org/openmbee/mms/users/security/DefaultUsersDetailsService.java @@ -1,31 +1,29 @@ -package org.openmbee.mms.localuser.security; - -import java.util.Collection; -import java.util.Optional; +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.localuser.config.UserPasswordRulesConfig; +import org.openmbee.mms.users.objects.UserCreateRequest; 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; +import java.util.Collection; +import java.util.Optional; + @Service -public class UserDetailsServiceImpl implements UserDetailsService { +public abstract class DefaultUsersDetailsService implements UsersDetailsService { + + public static final String TYPE = "local"; - private UserPersistence userPersistence; - private UserGroupsPersistence userGroupsPersistence; private PasswordEncoder passwordEncoder; private UserPasswordRulesConfig userPasswordRulesConfig; - @Autowired - public void setUserPersistence(UserPersistence userPersistence) { - this.userPersistence = userPersistence; - } + protected UserPersistence userPersistence; + protected UserGroupsPersistence userGroupsPersistence; @Autowired public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { @@ -43,30 +41,28 @@ public void setUserPasswordRulesConfig(UserPasswordRulesConfig userPasswordRules } @Override - public UserDetailsImpl loadUserByUsername(String username) throws UsernameNotFoundException { + 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 UserDetailsImpl(user.get(), userGroupsPersistence.findGroupsAssignedToUser(username)); - } - - public Collection getUsers() { - return userPersistence.findAll(); + 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.setFirstName(req.getFirstName()); + user.setLastName(req.getLastName()); user.setPassword(encodePassword(req.getPassword())); user.setEnabled(true); user.setAdmin(req.isAdmin()); - return userPersistence.save(user); + user.setType(DefaultUsersDetailsService.TYPE); + return saveUser(user); } public void changeUserPassword(String username, String password, boolean asAdmin) { @@ -84,10 +80,47 @@ 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()){ + user.setEnabled(req.isEnabled()); + } + 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/localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java b/users/src/main/java/org/openmbee/mms/users/security/UserPasswordRulesConfig.java similarity index 92% rename from localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java rename to users/src/main/java/org/openmbee/mms/users/security/UserPasswordRulesConfig.java index 05cf7cffc..34e8a11b0 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java +++ b/users/src/main/java/org/openmbee/mms/users/security/UserPasswordRulesConfig.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.localuser.config; +package org.openmbee.mms.users.security; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; 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