diff --git a/ac/src/intTest/java/ca/nrc/cadc/ac/integration/ConfigUsers.java b/ac/src/intTest/java/ca/nrc/cadc/ac/integration/ConfigUsers.java index 031a64d5..83e19f94 100644 --- a/ac/src/intTest/java/ca/nrc/cadc/ac/integration/ConfigUsers.java +++ b/ac/src/intTest/java/ca/nrc/cadc/ac/integration/ConfigUsers.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * - * (c) 2025. (c) 2025. + * (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -119,8 +119,7 @@ public class ConfigUsers { private static final String ANON_CERT_FILE = "ac-anon-user.pem"; private static final String PRIV_CERT_FILE = "ac-priv-user.pem"; - public static final String AC_SERVICE_ID = "ivo://opencadc.org/gms"; // TODO make configurable - //public static final String AC_SERVICE_ID = "ivo://cadc.nrc.ca/gms"; // TODO make configurable + public static final String AC_SERVICE_ID = "ivo://opencadc.org/ac"; // TODO make configurable PasswordAuthentication passwordAuthUser; private static ConfigUsers instance; diff --git a/ac/src/intTest/java/org/opencadc/ac/UserGroupMapTest.java b/ac/src/intTest/java/ca/nrc/cadc/ac/integration/UserMapTest.java similarity index 84% rename from ac/src/intTest/java/org/opencadc/ac/UserGroupMapTest.java rename to ac/src/intTest/java/ca/nrc/cadc/ac/integration/UserMapTest.java index 829347a9..143acd84 100644 --- a/ac/src/intTest/java/org/opencadc/ac/UserGroupMapTest.java +++ b/ac/src/intTest/java/ca/nrc/cadc/ac/integration/UserMapTest.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2025. (c) 2025. +* (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -65,9 +65,8 @@ ************************************************************************ */ -package org.opencadc.ac; +package ca.nrc.cadc.ac.integration; -import ca.nrc.cadc.ac.integration.ConfigUsers; import ca.nrc.cadc.auth.PosixPrincipal; import ca.nrc.cadc.reg.client.RegistryClient; import ca.nrc.cadc.util.Log4jInit; @@ -86,8 +85,8 @@ * * @author pdowler */ -public class UserGroupMapTest { - private static final Logger log = Logger.getLogger(UserGroupMapTest.class); +public class UserMapTest { + private static final Logger log = Logger.getLogger(UserMapTest.class); static { Log4jInit.setLevel("org.opencadc.ac", Level.INFO); @@ -98,10 +97,10 @@ public void testUserMap() throws Exception { RegistryClient reg = new RegistryClient(); URI srv = URI.create(ConfigUsers.AC_SERVICE_ID); final PosixMapperClient pmc = new PosixMapperClient(srv); - + Iterator iter = Subject.doAs(ConfigUsers.getInstance().getOwnerSubject(), (PrivilegedExceptionAction>) () -> pmc.getUserMap()); - + Assert.assertNotNull(iter); Assert.assertTrue(iter.hasNext()); log.info("obtained uidmap:"); @@ -109,24 +108,7 @@ public void testUserMap() throws Exception { PosixPrincipal pp = iter.next(); log.info(pp.username + " aka " + pp.getUidNumber() + ":" + pp.defaultGroup); } - - } - - @Test - public void testGroupMap() throws Exception { - RegistryClient reg = new RegistryClient(); - URI srv = URI.create("ivo://cadc.nrc.ca/gms"); - PosixMapperClient pmc = new PosixMapperClient(srv); - - Iterator iter = Subject.doAs(ConfigUsers.getInstance().getOwnerSubject(), - (PrivilegedExceptionAction>) () -> pmc.getGroupMap()); - Assert.assertNotNull(iter); - Assert.assertTrue(iter.hasNext()); - log.info("obtained uidmap:"); - while (iter.hasNext()) { - PosixGroup pg = iter.next(); - log.info(pg.getGroupURI() + " aka " + pg.getGID()); - } } + } diff --git a/ac/src/main/java/ca/nrc/cadc/ac/server/GetUserMapAction.java b/ac/src/main/java/ca/nrc/cadc/ac/server/GetUserMapAction.java index 2a2c65d2..237f1f3f 100644 --- a/ac/src/main/java/ca/nrc/cadc/ac/server/GetUserMapAction.java +++ b/ac/src/main/java/ca/nrc/cadc/ac/server/GetUserMapAction.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2023. (c) 2023. +* (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -77,13 +77,13 @@ import java.util.Collection; import java.util.List; import org.apache.log4j.Logger; -import static org.opencadc.ac.GetGroupMapAction.CONTENT_TYPE_TSV; /** * * @author pdowler */ public class GetUserMapAction extends RestAction { + private static final String CONTENT_TYPE_TSV = "text/tab-separated-values"; private static final Logger log = Logger.getLogger(GetUserMapAction.class); public GetUserMapAction() { diff --git a/ac/src/main/java/ca/nrc/cadc/ac/server/web/ServiceAvailability.java b/ac/src/main/java/ca/nrc/cadc/ac/server/web/ServiceAvailability.java index ca9648e2..fec0dd8b 100755 --- a/ac/src/main/java/ca/nrc/cadc/ac/server/web/ServiceAvailability.java +++ b/ac/src/main/java/ca/nrc/cadc/ac/server/web/ServiceAvailability.java @@ -2,7 +2,7 @@ ************************************************************************ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** * - * (c) 2025. (c) 2025. + * (c) 2026. (c) 2026. * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits reserves @@ -152,8 +152,5 @@ public Object run() throws Exception { } throw new CheckException(sb.toString()); } - - - } } diff --git a/ac/src/main/resources/PluginFactory.properties b/ac/src/main/resources/PluginFactory.properties index 4962b57d..862c19a1 100644 --- a/ac/src/main/resources/PluginFactory.properties +++ b/ac/src/main/resources/PluginFactory.properties @@ -3,8 +3,4 @@ ## change the configuration here ## UserPersistence implementation -ca.nrc.cadc.ac.server.UserPersistence = ca.nrc.cadc.ac.server.impl.UserPersistenceImpl - -## GroupPersistence implementation -ca.nrc.cadc.ac.server.GroupPersistence = ca.nrc.cadc.ac.server.impl.GroupPersistenceImpl - +ca.nrc.cadc.ac.server.UserPersistence = ca.nrc.cadc.ac.server.impl.UserPersistenceImpl \ No newline at end of file diff --git a/ac/src/main/webapp/WEB-INF/web.xml b/ac/src/main/webapp/WEB-INF/web.xml index 65078067..8a43eeaa 100644 --- a/ac/src/main/webapp/WEB-INF/web.xml +++ b/ac/src/main/webapp/WEB-INF/web.xml @@ -5,7 +5,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2019. (c) 2019. +* (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -76,17 +76,6 @@ index.html - - GroupMapServlet - ca.nrc.cadc.rest.RestServlet - - get - org.opencadc.ac.GetGroupMapAction - - - 3 - - UserMapServlet ca.nrc.cadc.rest.RestServlet @@ -101,32 +90,6 @@ - - GroupServlet - ca.nrc.cadc.rest.RestServlet - - init - org.opencadc.ac.InitGroupAction - - - get - org.opencadc.ac.GetAction - - - delete - org.opencadc.ac.DeleteAction - - - post - org.opencadc.ac.ModifyAction - - - put - org.opencadc.ac.CreateAction - - 2 - - ResetPasswordServlet ca.nrc.cadc.ac.server.web.ResetPasswordServlet @@ -215,29 +178,6 @@ 3 - - - ACSearchServlet - ca.nrc.cadc.uws.server.JobServlet - - get - ca.nrc.cadc.uws.web.SyncGetAction - - - post - ca.nrc.cadc.uws.web.SyncPostAction - - - ca.nrc.cadc.uws.web.SyncPostAction.execOnPOST - true - - - ca.nrc.cadc.uws.server.JobManager - ca.nrc.cadc.ac.server.web.ACSearchJobManager - - 2 - - @@ -410,60 +350,30 @@ logLevelPackages ca.nrc.cadc.ac - org.opencadc.ac ca.nrc.cadc.auth ca.nrc.cadc.rest ca.nrc.cadc.uws ca.nrc.cadc.vosi - - logControlProperties - ac-logControl.properties - 1 - - GroupMapServlet - /gidmap/* - - UserMapServlet /uidmap/* - - GroupServlet - /groups/* - - - - GroupServlet - /auth/groups/* - - UserServlet /users/* - - UserServlet - /auth/users/* - - UserRequestServlet /userRequests/* - - UserRequestServlet - /auth/userRequests/* - - LoginServlet /login @@ -474,31 +384,11 @@ /resetPassword - - ResetPasswordServlet - /auth/resetPassword - - - - ACSearchServlet - /search/* - - - - ACSearchServlet - /auth/search/* - - WhoAmIServlet /whoami - - WhoAmIServlet - /auth/whoami - - @@ -562,31 +452,4 @@ public - - - - auth - - /auth/* - GET - POST - HEAD - PUT - OPTIONS - TRACE - DELETE - - - force authentication for all requests - public - - - - - - BASIC - Canadian Astronomy Data Centre - diff --git a/ac/src/main/webapp/capabilities.xml b/ac/src/main/webapp/capabilities.xml index 1446fde5..1a2aaad9 100644 --- a/ac/src/main/webapp/capabilities.xml +++ b/ac/src/main/webapp/capabilities.xml @@ -67,40 +67,7 @@ - - - - https://replace.me.com/ac/groups - - - - - - - - - - - - https://replace.me.com/ac/search - - - - - - - - - - - - https://replace.me.com/ac/search - - - - - - + https://replace.me.com/ac/authorize @@ -135,15 +102,6 @@ - - - https://replace.me.com/ac/gidmap - - - - - - https://replace.me.com/ac/uidmap diff --git a/ac/src/main/webapp/service.yaml b/ac/src/main/webapp/service.yaml index 766d657c..d45aeb42 100644 --- a/ac/src/main/webapp/service.yaml +++ b/ac/src/main/webapp/service.yaml @@ -9,8 +9,6 @@ info: The following operations are available in the Access Control (AC) web service: - - Group Management - - Group Searching - User Management - User Login - Who Am I (Logged-in user info) @@ -42,281 +40,9 @@ info: id - The value identifying the user in the format specified by idType - Groups have an owner, a list of administrators and a list of members. The list of members and administrators can be composed of groups and users. The following describes the role of these properties of a group. The API sometimes requires the role to be specified in parameter 'role': - - - - owner - Can modify administrators list and members list. Can delete the group. - - - - admin - Can modify the administrators and members list. - - - - member - Are granted access to the resources the group is associated with. + Group management and group search are provided by the Group Management Service (GMS), not this AC API. basePath: /ac paths: - /groups: - get: - description: | - Lists the names of all the groups in the service - tags: - - Groups - schemes: - - https - produces: - - text/xml - responses: - '200': - description: Successful response - '403': - description: (forbidden) if the caller does not have read permission - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - put: - description: | - Create the group according to the group XML document in the HTTP PUT. - tags: - - Groups - schemes: - - https - consumes: - - text/xml - responses: - '200': - description: Successful response - '404': - description: 404 Not Found - If a member is not recognized. - '409': - description: 409 Conflict - If a group with the same name already exists. - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - '/groups/{groupID}': - get: - description: | - Get the group with name {groupID}. - tags: - - Groups - schemes: - - https - produces: - - text/xml - responses: - '200': - description: Successful response - '404': - description: '404 Not Found - If the group {groupID} could not be found.' - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - delete: - description: | - DELETE the group with name {groupID}. - tags: - - Groups - schemes: - - https - responses: - '200': - description: Successful response - '404': - description: '404 Not Found - If the group {groupID} could not be found.' - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - post: - description: | - Modify the group with name {groupID} according to the group XML document in the HTTP POST. - tags: - - Groups - schemes: - - https - consumes: - - text/xml - responses: - '200': - description: Successful response - '404': - description: '404 Not Found - If the group {groupID} could not be found.' - '409': - description: 409 Conflict - If a member group with the same name already exists. - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - parameters: - - name: groupID - in: path - description: The ID of the group - required: true - type: string - '/groups/{groupID}/userMembers/{userID}': - put: - description: | - Add user {userID} as a member of group {groupID}. - tags: - - Groups - schemes: - - https - consumes: - - text/xml - responses: - '200': - description: Successful response - '404': - description: '404 Not Found - If the group {groupID} could not be found or if the member {userID} is not recognized' - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - delete: - description: | - Remove user {userID} as a member of group {groupID}. - tags: - - Groups - schemes: - - https - consumes: - - text/xml - responses: - '200': - description: Successful response - '404': - description: '404 Not Found - If the group {groupID} could not be found or if the member {userID} is not recognized' - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - parameters: - - name: groupID - in: path - description: The ID of the group - required: true - type: string - - name: userID - in: path - description: The ID of the user member - required: true - type: string - - name: idType - in: query - description: The type of userID - required: true - type: string - enum: - - http - - x509 - - cadc - '/groups/{groupID2}/groupMembers/{groupID}': - put: - description: | - Add group {groupID2} as a member of group {groupID}. - tags: - - Groups - schemes: - - https - consumes: - - text/xml - responses: - '200': - description: Successful response - '404': - description: '404 Not Found - If the group {groupID} or group {groupID2} could not be found.' - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - delete: - description: | - Remove group {groupID2} as a member of group {groupID}. - tags: - - Groups - schemes: - - https - consumes: - - text/xml - responses: - '200': - description: Successful response - '404': - description: '404 Not Found - If the group {groupID} or {gropuID2} could not be found.' - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - parameters: - - name: groupID - in: path - description: The ID of the group to manage - required: true - type: string - - name: groupID2 - in: path - description: The ID of the group member. - required: true - type: string - /search: - get: - description: | - Find groups in which the calling user has the provided role. - tags: - - Membership searching - schemes: - - https - produces: - - text/xml - responses: - '200': - description: Successful response - '500': - description: Internal error - '503': - description: Service busy - default: - description: Unexpeced error - parameters: - - name: groupID - in: query - description: Search only the group with this ID. If groupID is supplied, role must also be supplied. - required: false - type: string - - name: role - in: query - description: The role to search. - required: false - type: string - default: member - enum: - - owner - - admin - - member - - name: group - in: query - description: An alias for the groupID parameters, but conforming to the IVOA GMS Standard. Multiple group parameters can be supplied and each one will be checked. The response list shows every group in which the calling user is a member. - required: false - type: string /users: get: description: | diff --git a/ac/src/test/java/ca/nrc/cadc/ac/server/impl/GroupPersistenceImplTest.java b/ac/src/test/java/ca/nrc/cadc/ac/server/impl/GroupPersistenceImplTest.java deleted file mode 100644 index 2d63b268..00000000 --- a/ac/src/test/java/ca/nrc/cadc/ac/server/impl/GroupPersistenceImplTest.java +++ /dev/null @@ -1,528 +0,0 @@ -/* -************************************************************************ -******************* CANADIAN ASTRONOMY DATA CENTRE ******************* -************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** -* -* (c) 2025. (c) 2025. -* Government of Canada Gouvernement du Canada -* National Research Council Conseil national de recherches -* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 -* All rights reserved Tous droits réservés -* -* NRC disclaims any warranties, Le CNRC dénie toute garantie -* expressed, implied, or énoncée, implicite ou légale, -* statutory, of any kind with de quelque nature que ce -* respect to the software, soit, concernant le logiciel, -* including without limitation y compris sans restriction -* any warranty of merchantability toute garantie de valeur -* or fitness for a particular marchande ou de pertinence -* purpose. NRC shall not be pour un usage particulier. -* liable in any event for any Le CNRC ne pourra en aucun cas -* damages, whether direct or être tenu responsable de tout -* indirect, special or general, dommage, direct ou indirect, -* consequential or incidental, particulier ou général, -* arising from the use of the accessoire ou fortuit, résultant -* software. Neither the name de l'utilisation du logiciel. Ni -* of the National Research le nom du Conseil National de -* Council of Canada nor the Recherches du Canada ni les noms -* names of its contributors may de ses participants ne peuvent -* be used to endorse or promote être utilisés pour approuver ou -* products derived from this promouvoir les produits dérivés -* software without specific prior de ce logiciel sans autorisation -* written permission. préalable et particulière -* par écrit. -* -* This file is part of the Ce fichier fait partie du projet -* OpenCADC project. OpenCADC. -* -* OpenCADC is free software: OpenCADC est un logiciel libre ; -* you can redistribute it and/or vous pouvez le redistribuer ou le -* modify it under the terms of modifier suivant les termes de -* the GNU Affero General Public la “GNU Affero General Public -* License as published by the License” telle que publiée -* Free Software Foundation, par la Free Software Foundation -* either version 3 of the : soit la version 3 de cette -* License, or (at your option) licence, soit (à votre gré) -* any later version. toute version ultérieure. -* -* OpenCADC is distributed in the OpenCADC est distribué -* hope that it will be useful, dans l’espoir qu’il vous -* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE -* without even the implied GARANTIE : sans même la garantie -* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ -* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF -* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence -* General Public License for Générale Publique GNU Affero -* more details. pour plus de détails. -* -* You should have received Vous devriez avoir reçu une -* a copy of the GNU Affero copie de la Licence Générale -* General Public License along Publique GNU Affero avec -* with OpenCADC. If not, see OpenCADC ; si ce n’est -* . pas le cas, consultez : -* . -* -* $Revision: 5 $ -* -************************************************************************ -*/ - -package ca.nrc.cadc.ac.server.impl; - - -import ca.nrc.cadc.ac.Group; -import ca.nrc.cadc.ac.GroupAlreadyExistsException; -import ca.nrc.cadc.ac.GroupNotFoundException; -import ca.nrc.cadc.ac.UserNotFoundException; -import ca.nrc.cadc.ac.server.IdentityManagerImpl; -import ca.nrc.cadc.auth.HttpPrincipal; -import ca.nrc.cadc.net.TransientException; -import ca.nrc.cadc.reg.Standards; -import ca.nrc.cadc.reg.client.LocalAuthority; -import ca.nrc.cadc.util.FileUtil; -import ca.nrc.cadc.util.Log4jInit; -import ca.nrc.cadc.util.PropertiesReader; -import java.io.File; -import java.net.URI; -import java.security.AccessControlException; -import java.security.PrivilegedExceptionAction; -import javax.security.auth.Subject; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.easymock.EasyMock; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.opencadc.ac.GroupsConfig; -import org.opencadc.gms.GroupURI; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - -/** - * - * @author pdowler - */ -public class GroupPersistenceImplTest -{ - private static final Logger log = Logger.getLogger(GroupPersistenceImplTest.class); - - private static final String ALLOWED_USER_PREFIX = "ALLOWED_USER-"; - private static final String ALLOWED_GROUP_PREFIX = "ALLOWED_USER_AND_GROUP-"; - - private static HttpPrincipal allowedPrincipal; - private static Subject allowedSubject; - private static Group allowedGroup; - private static Subject deniedSubject; - private static String configPath; - - @BeforeClass - public static void beforeClass() throws Exception - { - Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO); - - File conf = FileUtil.getFileFromResource(GroupPersistenceImpl.CONFIG_FILE, GroupPersistenceImplTest.class); - - allowedPrincipal = new HttpPrincipal("allowed-user"); - allowedSubject = new Subject(); - allowedSubject.getPrincipals().add(allowedPrincipal); - - HttpPrincipal dp = new HttpPrincipal("denied-user"); - deniedSubject = new Subject(); - deniedSubject.getPrincipals().add(dp); - - System.setProperty(PropertiesReader.CONFIG_DIR_SYSTEM_PROPERTY, "build/resources/test/config"); - GroupsConfig gc = new GroupsConfig(); - URI gmsServiceURI = gc.getResourceID(); - - LocalAuthority localAuthority = new LocalAuthority(); - - allowedGroup = new Group(new GroupURI(gmsServiceURI + "?allowed-group")); - - configPath = conf.getParent(); // build.xml copies it into same place - log.info("config path: " + configPath); - System.setProperty(PropertiesReader.class.getName() + ".dir", configPath); - } - - @Test - @Ignore - public void testGetAuthUserAndGroup() - { - try - { - System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); - - GroupPersistenceImpl impl = new GroupPersistenceImpl(); - - GroupPersistenceImpl.AuthUserAndGroup auth; - - try - { - auth = impl.getAuthUserAndGroup(null); - fail("null group name should throw exception"); - } - catch (IllegalArgumentException expected) {} - - auth = impl.getAuthUserAndGroup("foo"); - assertNull("should be null for unknown group name", auth); - - auth = impl.getAuthUserAndGroup(ALLOWED_USER_PREFIX + "123"); - assertNotNull("should not be null", auth); - assertNotNull(auth.authUser); - assertEquals(auth.authUser, allowedPrincipal); - assertNull(auth.authGroup); - - auth = impl.getAuthUserAndGroup(ALLOWED_GROUP_PREFIX + "123"); - assertNotNull("should not be null", auth); - assertNotNull(auth.authUser); - assertEquals(auth.authUser, allowedPrincipal); - assertNotNull(auth.authGroup); - assertEquals(auth.authGroup, allowedGroup); - } - catch(Exception unexpected) - { - log.error("unexpected exception", unexpected); - fail("unexpected exception: " + unexpected); - } - } - - @Test - @Ignore - public void testValidateGroupWithUser() - { - try - { - System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); - - GroupPersistenceImpl impl = new GroupPersistenceImpl(); - - // non-matching group names - GroupPersistenceImpl.AuthUserAndGroup authUserAndGroup; - authUserAndGroup = impl.validateGroupName(allowedSubject, "some-group"); - assertNull("auth user is not null", authUserAndGroup); - - authUserAndGroup = impl.validateGroupName(deniedSubject, "some-group"); - assertNull("auth user is not null", authUserAndGroup); - - // matching DENY- - try - { - impl.validateGroupName(allowedSubject, "DENY-123"); - fail("denied group prefix should throw AccessControlException"); - } - catch(AccessControlException expected) - { - log.debug("caught expected exception: " + expected); - } - - try - { - impl.validateGroupName(deniedSubject, "deny-123"); - fail("denied group prefix should throw AccessControlException"); - } - catch(AccessControlException expected) - { - log.debug("caught expected exception: " + expected); - } - - try - { - impl.validateGroupName(deniedSubject, "Deny-123"); - fail("denied group prefix should throw AccessControlException"); - } - catch(AccessControlException expected) - { - log.debug("caught expected exception: " + expected); - } - - // matching ALLOWED_USER- - authUserAndGroup = impl.validateGroupName(allowedSubject, "ALLOWED_USER-123"); - assertNotNull("auth user is null", authUserAndGroup); - assertEquals("", new HttpPrincipal("allowed-user"), authUserAndGroup.authUser); - - - // denied user - impl = new GroupPersistenceImpl() - { - @Override - boolean isMember(String groupID) throws UserNotFoundException, GroupNotFoundException, TransientException - { - return true; - } - }; - - try - { - impl.validateGroupName(deniedSubject, "ALLOWED_USER-123"); - fail("denied subject should throw AccessControlException"); - } - catch(AccessControlException expected) - { - log.debug("caught expected exception: " + expected); - } - } - catch(Exception unexpected) - { - log.error("unexpected exception", unexpected); - fail("unexpected exception: " + unexpected); - } - } - - @Test - @Ignore - public void testValidateGroupWithGroup() - { - try - { - System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); - - // non-matching ALLOWED_USER_AND_GROUP- - GroupPersistenceImpl impl = new GroupPersistenceImpl() - { - @Override - boolean isMember(String groupID) - throws UserNotFoundException, GroupNotFoundException, TransientException - { - return false; - } - }; - - try - { - impl.validateGroupName(deniedSubject, "ALLOWED_USER_AND_GROUP-123"); - fail("subject not in group should throw exception"); - } - catch (AccessControlException expected) {} - - // matching ALLOWED_USER_AND_GROUP- - impl = new GroupPersistenceImpl() - { - @Override - boolean isMember(String groupID) - throws UserNotFoundException, GroupNotFoundException, TransientException - { - return true; - } - }; - - GroupPersistenceImpl.AuthUserAndGroup authUserAndGroup = - impl.validateGroupName(deniedSubject, "ALLOWED_USER_AND_GROUP-123"); - assertNotNull("auth user is null", authUserAndGroup); - assertEquals("", new HttpPrincipal("allowed-user"), authUserAndGroup.authUser); - } - catch(Exception unexpected) - { - log.error("unexpected exception", unexpected); - fail("unexpected exception: " + unexpected); - } - } - - @Test - @Ignore - public void testAddGroupWithoutReservedPrefix() - { - try - { - System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); - - final GroupPersistenceImpl impl = new GroupPersistenceImpl() - { - Group g; - - @Override - AuthUserAndGroup validateGroupName(Subject caller, String gname) - throws GroupAlreadyExistsException, UserNotFoundException, TransientException - { - return null; - } - - @Override - Group superAddGroup(final Group group) - throws GroupAlreadyExistsException, TransientException, - UserNotFoundException, GroupNotFoundException - { - return g = group; - } - - @Override - Group superGetGroup(final Group group) - throws GroupAlreadyExistsException, TransientException, - UserNotFoundException, GroupNotFoundException - { - return g; - } - }; - - LocalAuthority localAuthority = new LocalAuthority(); - URI gmsServiceURI = localAuthority.getServiceURI(Standards.GMS_GROUPS_01.toString()); - - final Group expected = new Group(new GroupURI(gmsServiceURI + "?foo")); - - Group actual = Subject.doAs(allowedSubject, new PrivilegedExceptionAction() - { - @Override - public Group run() throws Exception - { - impl.addGroup(expected); - return impl.superGetGroup(expected); - } - }); - - assertNotNull("group is null", actual); - assertEquals("groups do not match", expected, actual); - } - catch(Exception unexpected) - { - log.error("unexpected exception", unexpected); - fail("unexpected exception: " + unexpected); - } - } - - @Test - @Ignore - public void testAddGroupWithReservedPrefix() - { - try - { - System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); - - final IdentityManagerImpl mockIM = EasyMock.createMock(IdentityManagerImpl.class); - mockIM.augmentSubject(allowedSubject); - EasyMock.expectLastCall().once(); - EasyMock.replay(mockIM); - - final GroupPersistenceImpl impl = new GroupPersistenceImpl() - { - Group g; - - @Override - AuthUserAndGroup validateGroupName(Subject caller, String gname) - throws GroupAlreadyExistsException, UserNotFoundException, TransientException - { - AuthUserAndGroup auth = new AuthUserAndGroup(); - auth.authUser = allowedPrincipal; - return auth; - } - - @Override - Group superAddGroup(final Group group) - throws GroupAlreadyExistsException, TransientException, - UserNotFoundException, GroupNotFoundException - { - return g = group; - } - - @Override - Group superGetGroup(final Group group) - throws GroupAlreadyExistsException, TransientException, - UserNotFoundException, GroupNotFoundException - { - return g; - } - - @Override - IdentityManagerImpl getIdentityManager() - { - return mockIM; - } - }; - - LocalAuthority localAuthority = new LocalAuthority(); - URI gmsServiceURI = localAuthority.getServiceURI(Standards.GMS_GROUPS_01.toString()); - final Group expected = new Group(new GroupURI(URI.create(gmsServiceURI + "?ALLOWED_USER-123"))); - - Group actual = Subject.doAs(allowedSubject, new PrivilegedExceptionAction() - { - @Override - public Group run() throws Exception - { - impl.addGroup(expected); - return impl.superGetGroup(expected); - } - }); - - assertNotNull("group is null", actual); - assertEquals("groups do not match", expected, actual); - - EasyMock.verify(mockIM); - } - catch(Exception unexpected) - { - log.error("unexpected exception", unexpected); - fail("unexpected exception: " + unexpected); - } - } - - @Test - @Ignore - public void testAddGroupGroupAlreadyExistsException() - { - try - { - System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); - - final IdentityManagerImpl mockIM = EasyMock.createMock(IdentityManagerImpl.class); - mockIM.augmentSubject(allowedSubject); - EasyMock.expectLastCall().once(); - EasyMock.replay(mockIM); - - final GroupPersistenceImpl impl = new GroupPersistenceImpl() - { - @Override - AuthUserAndGroup validateGroupName(Subject caller, String gname) - throws GroupAlreadyExistsException, UserNotFoundException, TransientException - { - AuthUserAndGroup auth = new AuthUserAndGroup(); - auth.authUser = allowedPrincipal; - return auth; - } - - @Override - Group superAddGroup(final Group group) - throws GroupAlreadyExistsException, TransientException, - UserNotFoundException, GroupNotFoundException - { - throw new GroupAlreadyExistsException("group exists"); - } - - @Override - IdentityManagerImpl getIdentityManager() - { - return mockIM; - } - }; - - LocalAuthority localAuthority = new LocalAuthority(); - URI gmsServiceURI = localAuthority.getServiceURI(Standards.GMS_GROUPS_01.toString()); - - final Group expected = new Group(new GroupURI(gmsServiceURI + "?ALLOWED_USER-123")); - - Subject.doAs(allowedSubject, new PrivilegedExceptionAction() - { - @Override - public Object run() throws Exception - { - try - { - impl.addGroup(expected); - fail("should have thrown GroupAlreadyExistsException"); - } - catch (GroupAlreadyExistsException e) - { - assertEquals("wrong message", "group exists", e.getMessage()); - } - return null; - } - }); - - EasyMock.verify(mockIM); - } - catch(Exception unexpected) - { - log.error("unexpected exception", unexpected); - fail("unexpected exception: " + unexpected); - } - } - -} diff --git a/ac/src/test/java/ca/nrc/cadc/ac/server/impl/UserPersistenceImplTest.java b/ac/src/test/java/ca/nrc/cadc/ac/server/impl/UserPersistenceImplTest.java index 8a11702e..9703e454 100644 --- a/ac/src/test/java/ca/nrc/cadc/ac/server/impl/UserPersistenceImplTest.java +++ b/ac/src/test/java/ca/nrc/cadc/ac/server/impl/UserPersistenceImplTest.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2025. (c) 2025. +* (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -92,12 +92,6 @@ public class UserPersistenceImplTest public static void beforeClass() throws Exception { Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO); - - File conf = FileUtil.getFileFromResource(GroupPersistenceImpl.CONFIG_FILE, UserPersistenceImplTest.class); - - String configPath = conf.getParent(); // build.xml copies it into same place - log.info("config path: " + configPath); - System.setProperty(PropertiesReader.class.getName() + ".dir", configPath); } @Test diff --git a/ac/src/test/java/org/opencadc/ac/AbstractActionTest.java b/ac/src/test/java/org/opencadc/ac/AbstractActionTest.java deleted file mode 100644 index 45eea1ee..00000000 --- a/ac/src/test/java/org/opencadc/ac/AbstractActionTest.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - ************************************************************************ - ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* - ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** - * - * (c) 2026. (c) 2026. - * Government of Canada Gouvernement du Canada - * National Research Council Conseil national de recherches - * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 - * All rights reserved Tous droits réservés - * - * NRC disclaims any warranties, Le CNRC dénie toute garantie - * expressed, implied, or énoncée, implicite ou légale, - * statutory, of any kind with de quelque nature que ce - * respect to the software, soit, concernant le logiciel, - * including without limitation y compris sans restriction - * any warranty of merchantability toute garantie de valeur - * or fitness for a particular marchande ou de pertinence - * purpose. NRC shall not be pour un usage particulier. - * liable in any event for any Le CNRC ne pourra en aucun cas - * damages, whether direct or être tenu responsable de tout - * indirect, special or general, dommage, direct ou indirect, - * consequential or incidental, particulier ou général, - * arising from the use of the accessoire ou fortuit, résultant - * software. Neither the name de l'utilisation du logiciel. Ni - * of the National Research le nom du Conseil National de - * Council of Canada nor the Recherches du Canada ni les noms - * names of its contributors may de ses participants ne peuvent - * be used to endorse or promote être utilisés pour approuver ou - * products derived from this promouvoir les produits dérivés - * software without specific prior de ce logiciel sans autorisation - * written permission. préalable et particulière - * par écrit. - * - * This file is part of the Ce fichier fait partie du projet - * OpenCADC project. OpenCADC. - * - * OpenCADC is free software: OpenCADC est un logiciel libre ; - * you can redistribute it and/or vous pouvez le redistribuer ou le - * modify it under the terms of modifier suivant les termes de - * the GNU Affero General Public la “GNU Affero General Public - * License as published by the License” telle que publiée - * Free Software Foundation, par la Free Software Foundation - * either version 3 of the : soit la version 3 de cette - * License, or (at your option) licence, soit (à votre gré) - * any later version. toute version ultérieure. - * - * OpenCADC is distributed in the OpenCADC est distribué - * hope that it will be useful, dans l’espoir qu’il vous - * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE - * without even the implied GARANTIE : sans même la garantie - * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ - * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF - * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence - * General Public License for Générale Publique GNU Affero - * more details. pour plus de détails. - * - * You should have received Vous devriez avoir reçu une - * a copy of the GNU Affero copie de la Licence Générale - * General Public License along Publique GNU Affero avec - * with OpenCADC. If not, see OpenCADC ; si ce n’est - * . pas le cas, consultez : - * . - * - * $Revision: 4 $ - * - ************************************************************************ - */ - -package org.opencadc.ac; - -import ca.nrc.cadc.auth.HttpPrincipal; -import ca.nrc.cadc.auth.IdentityType; -import ca.nrc.cadc.rest.SyncInput; -import ca.nrc.cadc.util.Log4jInit; -import ca.nrc.cadc.util.PropertiesReader; -import java.security.PrivilegedExceptionAction; -import javax.security.auth.Subject; -import javax.security.auth.x500.X500Principal; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -public class AbstractActionTest { - private static final Logger log = Logger.getLogger(AbstractActionTest.class); - - private AbstractAction abstractAction; - - @BeforeClass - public static void beforeClass() throws Exception { - Log4jInit.setLevel("org.opencadc.ac", Level.INFO); - } - - @Before - public void setUp() { - - abstractAction = new AbstractAction() { - @Override - public void doAction() { - - } - }; - } - - @Test - public void testSetPrivilegedSubject() throws Exception { - log.debug("testSetPrivilegedSubject: START"); - Subject privilegedSubject = new Subject(); - privilegedSubject.getPrincipals().add(new X500Principal("CN=Privileged")); - - System.setProperty(PropertiesReader.CONFIG_DIR_SYSTEM_PROPERTY, "build/resources/test/config"); - Subject.doAs(privilegedSubject, (PrivilegedExceptionAction) () -> { - abstractAction.setReadSubject(new GroupsConfig()); - return null; - }); - - assertNotNull(abstractAction.readSubject); - assertEquals(privilegedSubject, abstractAction.readSubject); - - // Test with non-privileged subject - abstractAction.readSubject = null; - Subject nonPrivilegedSubject = new Subject(); - Subject.doAs(nonPrivilegedSubject, (PrivilegedExceptionAction) () -> { - abstractAction.setReadSubject(new GroupsConfig()); - return null; - }); - assertNull(abstractAction.readSubject); - - } - - @Test - public void testSetRequestInputValidPath() { - checkPath("/groupName/userMembers/memberName", "groupName", "memberName", IdentityType.USERNAME.getValue()); - checkPath("/groupName/userMembers/memberName?idType=HTTP", "groupName", "memberName", IdentityType.USERNAME.getValue()); - checkPath("/groupName/userMembers/cn=memberName?idType=X500", "groupName", "cn=memberName", IdentityType.X500.getValue()); - checkPath("/groupName/userMembers/123?idType=CADC", "groupName", "123", IdentityType.CADC.getValue()); - - checkPath("/groupName/groupMembers/memberGroup", "groupName", "memberGroup", null); - } - - private void checkPath(String path, String expectedGroupName, String expectedMemberName, String expectedUserIDType) { - abstractAction.setSyncInput(new SyncInput() { - @Override - public String getPath() { - return path.split("\\?")[0]; - } - - @Override - public String getParameter(String name) { - if (path.contains("?")) { - return path.split("\\?")[1].split("idType=")[1]; - } - return null; - } - }); - abstractAction.setRequestInput(); - AbstractAction.RequestInput requestInput = abstractAction.requestInput; - assertEquals(expectedGroupName, requestInput.groupName); - assertEquals(expectedMemberName, requestInput.memberName); - assertEquals(expectedUserIDType, requestInput.userIDType); - } - - @Test(expected = IllegalArgumentException.class) - public void testSetRequestInputInvalidPath() { - abstractAction.setSyncInput(new SyncInput() { - @Override - public String getPath() { - return "/invalid/path/with/too/many/segments"; - } - - @Override - public String getParameter(String name) { - return null; - } - }); - - abstractAction.setRequestInput(); - } -} diff --git a/cadc-access-control-admin/src/main/java/ca/nrc/cadc/ac/admin/CmdLineParser.java b/cadc-access-control-admin/src/main/java/ca/nrc/cadc/ac/admin/CmdLineParser.java index e7f090e4..82c7bb3f 100644 --- a/cadc-access-control-admin/src/main/java/ca/nrc/cadc/ac/admin/CmdLineParser.java +++ b/cadc-access-control-admin/src/main/java/ca/nrc/cadc/ac/admin/CmdLineParser.java @@ -381,8 +381,6 @@ public static String getUsage() { sb.append("\n"); sb.append("Authentication and authorization:\n"); sb.append(" - An ac-ldap-config.properties file must exist in directory ~/config/\n"); - sb.append(" - The corresponding host entry (devLdap or prodLdap) must exist\n"); - sb.append(" in your ~/.dbrc file."); return sb.toString(); } diff --git a/cadc-access-control-server/.dbrc_example b/cadc-access-control-server/.dbrc_example deleted file mode 100644 index e57afd54..00000000 --- a/cadc-access-control-server/.dbrc_example +++ /dev/null @@ -1,2 +0,0 @@ -#server proxyuser proxyUserDN password driver serverURL - N/A N/A diff --git a/cadc-access-control-server/README.md b/cadc-access-control-server/README.md index 3e879c50..a21c4041 100644 --- a/cadc-access-control-server/README.md +++ b/cadc-access-control-server/README.md @@ -21,7 +21,7 @@ relational database). | operation | HTTP Method | description | faults | |----------------------------------------------|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| | Group Management - List all groups | GET | Lists the names of all the groups in the service | | -| Group Management - Create group | PUT | Ceate the group according to the group XML document in the HTTP PUT. | 404 Not Found - If a member is not recognized. | 409 Conflict - If a group with the same name already exists. | +| Group Management - Create group | PUT | Create the group according to the group XML document in the HTTP PUT. | 404 Not Found - If a member is not recognized.
409 Conflict - If a group with the same name already exists. | | Group Management - Get group | GET | Get the group with name {groupID}. | 404 Not Found - If the group {groupID} could not be found. | | Group Management - Delete group | DELETE | DELETE the group with name {groupID}. | 404 Not Found - If the group {groupID} could not be found. | | Group Management - Modify group | POST | Modify the group with name {groupID} according to the group XML document in the HTTP POST. | 404 Not Found - If the group {groupID} could not be found or if a member is not recognized
409 Conflict - If a group with the same name already exists. | @@ -29,66 +29,74 @@ relational database). | Group Management - Remove user member | DELETE | Remove user {userID} as a member of group {groupID}. | 404 Not Found - If the group {groupID} could not be found or if the member {userID} is not recognized | | Group Management - Add group member | PUT | Add group {groupID2} as a member of group {groupID}. | 404 Not Found - If the group {groupID} or {groupID2} could not be found. | | Group Management - Remove group member | DELETE | Remove group {groupID2} as a member of group {groupID}. | 404 Not Found - If the group {groupID} or {groupID2} could not be found. | -| Group Searching - Search by role | DELETE Find the groups in which the user (specified by param {userID}) has the role {role}. | | +| Group Searching - Search by role | GET | Find the groups in which the user (specified by param {userID}) has the role {role}. | | | Group Searching - Search specific membership | POST | If a user has the specified role in the specified group the group is returned. Otherwise returns an empty list of groups. | | | User management - List all users | GET | Lists basic information of all the users in the service | | | User Management - Request account | PUT | Request the user account in the user XML document in the HTTP PUT. This can take an arbitrary amount of time. If the account existed before but was deleted, this operation will reenable the account. | 404 Not Found - If a member is not recognized.
409 Conflict - If a group with the same name already exists. | | User Management - Get user | GET | Get the user with userid {userID} of type {idType}. This operation supports an optional parameter: detail, which can have values of display or identity. The detail parameter adjusts the amount type of user information that is returned. | 404 Not Found - If the user {userID} could not be found. | | User Management - Disable account | DELETE | Disable the account for user with userid {userID} of type {idType}. | 404 Not Found - If the group {userID} could not be found. | -| User Management - Modify user | POST | Modify the user with name {userID} and type {idType{ according to the user XML document in the HTTP POST. | 404 Not Found - If the user {userID} could not be found. | -| User Login | POST | Validate the userID and password combination. If the combination is valid this operation will return a cookie that can be used to enter any of these endpoints over HTTP. | 403 Permission Denied - If the userID / pasword validation failed. | -| Password Changes | POST | Change password from {old_password} to {new_password}. | 403 Permission Denied - If the old pasword is incorrect. | -| Who Am I - Logged-in user information | GET | Returns information about the authentication user. | | +| User Management - Modify user | POST | Modify the user with name {userID} and type {idType} according to the user XML document in the HTTP POST. | 404 Not Found - If the user {userID} could not be found. | +| User Login | POST | Validate the userID and password combination. If the combination is valid this operation will return a cookie that can be used to enter any of these endpoints over HTTP. | 403 Permission Denied - If the userID / password validation failed. | +| Password Changes | POST | Change password from {old_password} to {new_password}. | 403 Permission Denied - If the old password is incorrect. | +| Who Am I - Logged-in user information | GET | Returns information about the authenticated user. | | | Service availability | GET | | | ## CONFIGURATION -The service requires the following configuration files: -- `ac-ldap-config.properties`: stores LDAP connection and pool configuration +See the [cadc-java](https://github.com/opencadc/docker-base/tree/master/cadc-java) +image docs for general config requirements. + +Runtime configuration must be made available via the `/config` directory. ### ac-ldap-config.properties -This file configures connection to the back-end LDAP server. Three connection pools must be defined: -read-only, read-write, and unbound read-only. +This file configures connection to the back-end LDAP server. A template is provided in +[this module](ac-ldap-config.properties). + +All three connection pools must be configured. Setting a pool's `poolMaxSize` to `0` affects +service availability: + +- `readOnly.poolMaxSize = 0` or `unboundReadOnly.poolMaxSize = 0` puts the service in **offline** mode +- `readWrite.poolMaxSize = 0` puts the service in **read-only** mode ``` ################## Read-only connection pool ################## -# space separated list of hosts +# space-separated list of hosts readOnly.servers = {ldap server} readOnly.port = 389 readOnly.secure = false readOnly.poolInitSize = 1 readOnly.poolMaxSize = 1 -# +# roundRobin || fewestConnections || fastestConnect readOnly.poolPolicy = roundRobin readOnly.maxWait = 30000 readOnly.createIfNeeded = false ################## Read-write connection pool ################# -# space separated list of hosts +# space-separated list of hosts readWrite.servers = {ldap server} readWrite.port = 636 readWrite.secure = true readWrite.poolInitSize = 1 readWrite.poolMaxSize = 1 -# +# roundRobin || fewestConnections readWrite.poolPolicy = roundRobin readWrite.maxWait = 30000 readWrite.createIfNeeded = false -############## Unbound-Read-only connection pool ############## -# space separated list of hosts +############## Unbound-read-only connection pool ############## +# space-separated list of hosts unboundReadOnly.servers = {ldap server} unboundReadOnly.port = 636 unboundReadOnly.secure = true unboundReadOnly.poolInitSize = 1 unboundReadOnly.poolMaxSize = 1 -# +# roundRobin || fewestConnections unboundReadOnly.poolPolicy = roundRobin unboundReadOnly.maxWait = 30000 unboundReadOnly.createIfNeeded = false -########## server configuration -- applies to all servers ##### +########## server configuration -- applies to all pools ####### port = 636 proxyUser = uid=webproxy,ou=SpecialUsers,dc=canfar,dc=net proxyPassword = {webproxy ldap password} @@ -103,7 +111,29 @@ Each pool may specify its own `port`. If omitted, the default `port` at the bott Each pool may also specify `secure` to indicate whether connections use TLS. When omitted, `secure` defaults to `true` when the pool port is 636 and `false` otherwise. Set `secure = true` explicitly when using a non-standard port for LDAPS (for example, `readOnly.port = 10636` with -`readOnly.secure = true`). - -See also [ac-ldap-config.properties](ac-ldap-config.properties) for a template with all supported keys. +`readOnly.secure = true`). Pools that share the same port must use the same `secure` setting. + +Property summary: + +| property | required | description | +|----------|----------|-------------| +| `{pool}.servers` | yes | Space-separated list of LDAP host names | +| `{pool}.poolInitSize` | yes | Initial number of connections in the pool | +| `{pool}.poolMaxSize` | yes | Maximum number of connections in the pool | +| `{pool}.poolPolicy` | yes | Load-balancing policy for the pool | +| `{pool}.maxWait` | yes | Connection wait timeout in milliseconds | +| `{pool}.createIfNeeded` | yes | Whether to create connections beyond `poolMaxSize` | +| `{pool}.port` | no | LDAP port for this pool; defaults to `port` | +| `{pool}.secure` | no | Whether the pool uses TLS; defaults from the pool port | +| `port` | yes* | Default LDAP port (389 or 636) when a pool port is omitted | +| `proxyUser` | yes | DN of the LDAP proxy user | +| `proxyPassword` | yes | Password for the LDAP proxy user | +| `usersDN` | yes | DN of the users branch | +| `userRequestsDN` | yes | DN of the new-user-requests branch | +| `groupsDN` | yes | DN of the groups branch | +| `adminGroupsDN` | yes | DN of the admin-groups branch | + +\* Required unless every pool specifies its own `{pool}.port`. + +The `fastestConnect` pool policy is supported for the read-only pool only. diff --git a/cadc-access-control/build.gradle b/cadc-access-control/build.gradle index 9a6daf37..de2ac2fd 100644 --- a/cadc-access-control/build.gradle +++ b/cadc-access-control/build.gradle @@ -16,7 +16,7 @@ sourceCompatibility = 11 group = 'org.opencadc' -version = '1.1.31' +version = '1.1.32' description = 'OpenCADC User+Group client library' def git_url = 'https://github.com/opencadc/ac' diff --git a/cadc-access-control/src/main/java/ca/nrc/cadc/ac/client/GMSClient.java b/cadc-access-control/src/main/java/ca/nrc/cadc/ac/client/GMSClient.java index 75993b9e..6c7ea700 100755 --- a/cadc-access-control/src/main/java/ca/nrc/cadc/ac/client/GMSClient.java +++ b/cadc-access-control/src/main/java/ca/nrc/cadc/ac/client/GMSClient.java @@ -257,7 +257,7 @@ private List getMemberships(Principal ignore, Role role) } String roleString = role.getValue(); - URL searchURL = lookupServiceURL(Standards.GMS_SEARCH_01); + URL searchURL = lookupServiceURL(Standards.GMS_SEARCH_10); URL getMembershipsURL = new URL(searchURL.toExternalForm() + "?ROLE=" + NetUtil.encode(roleString)); @@ -830,7 +830,7 @@ public Group getMembership(String groupName, Role role) String searchGroupPath = "?ROLE=" + NetUtil.encode(roleString) + "&GROUPID=" + NetUtil.encode(groupName); - URL searchURL = lookupServiceURL(Standards.GMS_SEARCH_01); + URL searchURL = lookupServiceURL(Standards.GMS_SEARCH_10); URL getMembershipURL = new URL(searchURL.toExternalForm() + searchGroupPath); log.debug("getMembership request to " + getMembershipURL.toString()); diff --git a/gms/Dockerfile b/gms/Dockerfile new file mode 100644 index 00000000..bfd41bd8 --- /dev/null +++ b/gms/Dockerfile @@ -0,0 +1,3 @@ +FROM images.opencadc.org/library/cadc-tomcat:1 + +COPY build/libs/gms.war /usr/share/tomcat/webapps diff --git a/gms/README.md b/gms/README.md new file mode 100644 index 00000000..38aa5fbf --- /dev/null +++ b/gms/README.md @@ -0,0 +1,97 @@ +# Groups Management Service (gms) + +The ***gms*** service provides RESTful web service interface for group management operations. It handles creation, modification, deletion, and querying of user groups, as well as managing group memberships and permissions. + +This service works with a user service that provides user identity information. TBD + +## deployment +The `gms` war file can be renamed at deployment time in order to support an alternate service name, including +introducing additional path elements. +See cadc-tomcat (war-rename.conf). + +## configuration +The following runtime configuration must be made available via the `/config` directory. + +### catalina.properties +This file contains java system properties to configure the tomcat server and some of the java libraries +used in the service. + +See cadc-tomcat for +system properties related to the deployment environment. + +See cadc-util for common system properties. + +### cadc-registry.properties + +See cadc-registry. + +### ac-ldap-config.properties + +See cadc-access-control-server. + +### gms.properties +TBD. At minimum, the following properties are required: +- Corresponding user service base URL and the name of the user identity attribute. + +## API Overview + +The GMS service provides the following operations: + +### Group Management +- **List all groups** - GET /groups +- **Create group** - PUT /groups +- **Get group** - GET /groups/{groupID} +- **Delete group** - DELETE /groups/{groupID} +- **Modify group** - POST /groups/{groupID} +- **Add/Remove user members** - POST/DELETE /groups/{groupID}/userMembers +- **Add/Remove group members** - POST/DELETE /groups/{groupID}/groupMembers (Is it required to distinguish between user and group members?) + +### Group Searching +- **Search by role** - GET /search?id={userID}&idType={idType}&role={role} +- **Search specific membership** - GET /search?id={userID}&idType={idType}&role={role}&groupID={groupID} + +### Authentication Methods +The service supports multiple authentication methods: +- **Client certificates** (CC) over HTTPS - `/groups/*` endpoints +- **Anonymous** (AN) access for listing operations + +## Group Structure + +Groups have the following key components: +- **Owner** - Can modify administrator/member lists and delete the group +- **Administrators** - Can modify administrator and member lists +- **Members** - Are granted access to resources the group is associated with + +Both users and other groups can be members or administrators of a group. + +## building it +``` +gradle clean build +docker build -t gms -f Dockerfile . +``` + +## running it +``` +docker run --rm --user tomcat:tomcat --volume=/path/to/external/config:/config:ro --name gms gms:latest +``` + +## testing it + +### Unit tests +``` +gradle clean test +``` + +### Integration tests +``` +gradle clean intTest +``` + +For local testing against a running instance: +``` +~/bin/int-test-localhost.sh +``` + +## API Documentation + +For detailed API specifications including request/response formats, authentication requirements, and error codes, see the OpenAPI specification or the service capabilities endpoint at `/capabilities`. diff --git a/gms/build.gradle b/gms/build.gradle new file mode 100644 index 00000000..6fffdf99 --- /dev/null +++ b/gms/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'war' + id 'checkstyle' +} + +repositories { + mavenCentral() + mavenLocal() +} + +apply from: '../opencadc.gradle' + +sourceCompatibility = 11 + +group = 'ca.nrc.cadc' + +war { + // Include the swagger-ui so that /ac provides the Access Control API documentation + from(System.getenv('RPS') + '/resources/') { + include 'swagger-ui/' + } + from('.') { + include 'VERSION' + } +} + +dependencies { + implementation 'javax.servlet:javax.servlet-api:[3.1.0,)' + implementation 'org.jdom:jdom2:[2.0,)' + implementation 'org.json:json:20250517' + + implementation 'org.opencadc:cadc-registry:[1.7,)' + implementation 'org.opencadc:cadc-util:[1.9.11,)' + implementation 'org.opencadc:cadc-rest:[1.3.10,)' + implementation 'org.opencadc:cadc-log:[1.1.5,)' + implementation 'org.opencadc:cadc-access-control:[1.1.32,)' + implementation 'org.opencadc:cadc-access-control-server:[1.3.38,)' + implementation 'org.opencadc:cadc-vosi:[1.4.4,2.0)' + + runtimeOnly 'org.opencadc:cadc-access-control-identity:[1.1.0,)' + + testImplementation 'junit:junit:[4.0,)' + testImplementation 'org.easymock:easymock:[3.0,)' + testImplementation 'xerces:xercesImpl:[2.0,)' + testImplementation 'org.skyscreamer:jsonassert:[1.0,)' + testImplementation 'com.unboundid:unboundid-ldapsdk:[2.3,3.0)' + + intTestImplementation 'org.opencadc:cadc-test-vosi:[1.0.2,)' + testImplementation 'org.opencadc:cadc-test-vosi:[1.0.11,)' + testImplementation 'com.nimbusds:nimbus-jose-jwt:[9.22,)' +} diff --git a/gms/model.puml b/gms/model.puml new file mode 100644 index 00000000..2d8fa212 --- /dev/null +++ b/gms/model.puml @@ -0,0 +1,65 @@ +@startuml GMS Domain Model + +class Group { + - uri: URI + - description: String + - gid: Integer + - lastModified: Date + - owner: User + - userMembers: Set + - userAdmins: Set + - groupMembers: Set + - groupAdmins: Set +} + +class User { + - identity: Identity + - firstName: String + - lastName: String + - email: String +} + +class Identity { + - type: String + - value: String +} + +enum IdentityType { + X500 + OPENID + NUMERIC +} + +enum Role { + OWNER + ADMIN + MEMBER +} + +' Relationships +Group "1" *-- "1" User : owner +Group "1" o-- "0..*" User : userMembers +Group "1" o-- "0..*" User : userAdmins +Group "1" o-- "0..*" Group : groupMembers +Group "1" o-- "0..*" Group : groupAdmins + +User "1" *-- "1" Identity : key identity + +Identity ..> IdentityType : <> +Group ..> Role : <> + +note right of Group + A group can contain both + users and other groups as + members or administrators +end note + +note left of User + Users are identified by + a configurable identity + (primary key) that the + corresponding user service + provides +end note + +@enduml diff --git a/gms/src/intTest/java/org/opencadc/gms/ConfigUsers.java b/gms/src/intTest/java/org/opencadc/gms/ConfigUsers.java new file mode 100644 index 00000000..de76b67e --- /dev/null +++ b/gms/src/intTest/java/org/opencadc/gms/ConfigUsers.java @@ -0,0 +1,232 @@ +/* + ************************************************************************ + ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* + ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** + * + * (c) 2026. (c) 2026. + * Government of Canada Gouvernement du Canada + * National Research Council Conseil national de recherches + * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 + * All rights reserved Tous droits réservés + * + * NRC disclaims any warranties, Le CNRC dénie toute garantie + * expressed, implied, or énoncée, implicite ou légale, + * statutory, of any kind with de quelque nature que ce + * respect to the software, soit, concernant le logiciel, + * including without limitation y compris sans restriction + * any warranty of merchantability toute garantie de valeur + * or fitness for a particular marchande ou de pertinence + * purpose. NRC shall not be pour un usage particulier. + * liable in any event for any Le CNRC ne pourra en aucun cas + * damages, whether direct or être tenu responsable de tout + * indirect, special or general, dommage, direct ou indirect, + * consequential or incidental, particulier ou général, + * arising from the use of the accessoire ou fortuit, résultant + * software. Neither the name de l'utilisation du logiciel. Ni + * of the National Research le nom du Conseil National de + * Council of Canada nor the Recherches du Canada ni les noms + * names of its contributors may de ses participants ne peuvent + * be used to endorse or promote être utilisés pour approuver ou + * products derived from this promouvoir les produits dérivés + * software without specific prior de ce logiciel sans autorisation + * written permission. préalable et particulière + * par écrit. + * + * This file is part of the Ce fichier fait partie du projet + * OpenCADC project. OpenCADC. + * + * OpenCADC is free software: OpenCADC est un logiciel libre ; + * you can redistribute it and/or vous pouvez le redistribuer ou le + * modify it under the terms of modifier suivant les termes de + * the GNU Affero General Public la “GNU Affero General Public + * License as published by the License” telle que publiée + * Free Software Foundation, par la Free Software Foundation + * either version 3 of the : soit la version 3 de cette + * License, or (at your option) licence, soit (à votre gré) + * any later version. toute version ultérieure. + * + * OpenCADC is distributed in the OpenCADC est distribué + * hope that it will be useful, dans l’espoir qu’il vous + * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE + * without even the implied GARANTIE : sans même la garantie + * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ + * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF + * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence + * General Public License for Générale Publique GNU Affero + * more details. pour plus de détails. + * + * You should have received Vous devriez avoir reçu une + * a copy of the GNU Affero copie de la Licence Générale + * General Public License along Publique GNU Affero avec + * with OpenCADC. If not, see OpenCADC ; si ce n’est + * . pas le cas, consultez : + * . + * + * $Revision: 4 $ + * + ************************************************************************ + */ + +package org.opencadc.gms; + +import ca.nrc.cadc.ac.client.UserClient; +import ca.nrc.cadc.auth.AuthMethod; +import ca.nrc.cadc.auth.HttpPrincipal; +import ca.nrc.cadc.auth.SSLUtil; +import ca.nrc.cadc.net.NetrcFile; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.util.FileUtil; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URL; +import java.security.PrivilegedExceptionAction; +import java.util.Set; +import javax.security.auth.Subject; +import org.apache.log4j.Logger; +import org.junit.Assert; +import static org.junit.Assert.assertEquals; + +/** + * Common class to produce authentication credentials for different types of users of the ac system configured through + * their X509 certificates. + * + * AC Integration tests require the following users (name of corresponding cert files) + * - ac-group-owner.pem : owner of test group + * - ac-group-member.pem : member of test group + * - ac-registered-user.pem : registered user but not a member in any group, e.g. authenticated but not authorized + * - ac-anon-user.pem : unregistered user + * - ac-priv-user.pem : privileged user capable of augmenting subject + * - ~/.netrc entry for the host part of the ac service URL with login and password for a registered user. It can be + * one of the above users. + * + * @author andamian + */ +public class ConfigUsers { + private static final Logger log = Logger.getLogger(ConfigUsers.class); + + private String ownerUsername; + private String memberUsername; + private String registeredUsername; + + private Subject augmentedOwnerSubject; + + private static final String ONWER_CERT_FILE = "ac-group-owner.pem"; + private static final String MEMBER_CERT_FILE = "ac-group-member.pem"; + private static final String REGISTERED_CERT_FILE = "ac-registered-user.pem"; + private static final String ANON_CERT_FILE = "ac-anon-user.pem"; + private static final String PRIV_CERT_FILE = "ac-priv-user.pem"; + + public static final String GMS_SERVICE_ID = "ivo://opencadc.org/gms"; // TODO make configurable + public static final String AC_SERVICE_ID = "ivo://opencadc.org/ac"; // TODO make configurable + + PasswordAuthentication passwordAuthUser; + private static ConfigUsers instance; + + private ConfigUsers() { + log.debug("User serviceURI: " + AC_SERVICE_ID); + } + + public static ConfigUsers getInstance() { + if (instance == null) { + instance = new ConfigUsers(); + } + return instance; + } + + public String getOwnerUsername() { + if (ownerUsername == null) { + ownerUsername = getUsername(ONWER_CERT_FILE); + } + return ownerUsername; + } + + public Subject getOwnerSubject() { + return SSLUtil.createSubject(FileUtil.getFileFromResource(ONWER_CERT_FILE, ConfigUsers.class)); + } + + public Subject getAugmentedOwnerSubject() { + if (augmentedOwnerSubject == null) { + augmentedOwnerSubject = getAugmentedSubject(ONWER_CERT_FILE); + } + return augmentedOwnerSubject; + } + + public String getMemberUsername() { + if (memberUsername == null) { + memberUsername = getUsername(MEMBER_CERT_FILE); + } + return memberUsername; + } + + public Subject getMemberSubject() { + return SSLUtil.createSubject(FileUtil.getFileFromResource(MEMBER_CERT_FILE, ConfigUsers.class)); + } + + public String getRegisteredUsername() { + if (registeredUsername == null) { + registeredUsername = getUsername(REGISTERED_CERT_FILE); + } + return registeredUsername; + } + + public Subject getRegisteredSubject() { + return SSLUtil.createSubject(FileUtil.getFileFromResource(REGISTERED_CERT_FILE, ConfigUsers.class)); + } + + public Subject getAnonSubject() { + return SSLUtil.createSubject(FileUtil.getFileFromResource(ANON_CERT_FILE, ConfigUsers.class)); + } + + public Subject getPrivSubject() { + return SSLUtil.createSubject(FileUtil.getFileFromResource(PRIV_CERT_FILE, ConfigUsers.class)); + } + + + public Subject getAugmentedSubject(String certFile) { + Subject subject = SSLUtil.createSubject(FileUtil.getFileFromResource(certFile, ConfigUsers.class)); + try { + Subject.doAs(subject, new PrivilegedExceptionAction() { + @Override + public Object run() throws Exception { + UserClient client = new UserClient(new URI(AC_SERVICE_ID)); + client.augmentSubject(subject); + return null; + } + + }); + return subject; + } catch (IllegalArgumentException e) { + throw e; + } catch (Exception e) { + log.error("unexpected", e); + Assert.fail("Caught an unexpected exception: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + private String getUsername(String certFile) { + Subject subject = getAugmentedSubject(certFile); + final Set httpPrincipals = subject.getPrincipals(HttpPrincipal.class); + assertEquals("Expected exactly one HttpPrincipal in subject for cert: " + certFile, 1, httpPrincipals.size()); + return httpPrincipals.iterator().next().getName(); + } + + /** + * Get the credentials for a registered user from the .netrc file. + * The .netrc file must contain an entry for the host part of the ac service URL. + * @return PasswordAuthentication containing the login and password + */ + public PasswordAuthentication getPasswordAuthUser() { + if (passwordAuthUser == null) { + NetrcFile netrc = new NetrcFile(); + RegistryClient regClient = new RegistryClient(); + URL loginUrl = regClient + .getServiceURL(URI.create(ConfigUsers.GMS_SERVICE_ID), Standards.UMS_LOGIN_01, AuthMethod.ANON); + log.info("loginUrl: " + loginUrl); + passwordAuthUser = netrc.getCredentials(loginUrl.getHost(), true); + Assert.assertNotNull("~/.netrc credentials required for host: " + loginUrl.getHost(), passwordAuthUser); + } + return passwordAuthUser; + } +} diff --git a/ac/src/intTest/java/org/opencadc/ac/GmsClientIntTest.java b/gms/src/intTest/java/org/opencadc/gms/GmsClientIntTest.java similarity index 99% rename from ac/src/intTest/java/org/opencadc/ac/GmsClientIntTest.java rename to gms/src/intTest/java/org/opencadc/gms/GmsClientIntTest.java index bc699d4c..d610d781 100644 --- a/ac/src/intTest/java/org/opencadc/ac/GmsClientIntTest.java +++ b/gms/src/intTest/java/org/opencadc/gms/GmsClientIntTest.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * - * (c) 2025. (c) 2025. + * (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.ac.Group; import ca.nrc.cadc.ac.GroupAlreadyExistsException; @@ -75,7 +75,6 @@ import ca.nrc.cadc.ac.Role; import ca.nrc.cadc.ac.User; import ca.nrc.cadc.ac.client.GMSClient; -import ca.nrc.cadc.ac.integration.ConfigUsers; import ca.nrc.cadc.auth.AuthenticationUtil; import ca.nrc.cadc.auth.HttpPrincipal; import ca.nrc.cadc.util.Log4jInit; @@ -96,7 +95,6 @@ import org.apache.log4j.Logger; import org.junit.Assert; import org.junit.Test; -import org.opencadc.gms.GroupURI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -116,12 +114,12 @@ public class GmsClientIntTest private User unknownUser; private Group unknownGroup; - private URI serviceURI = URI.create(ConfigUsers.AC_SERVICE_ID); + private URI serviceURI = URI.create(ConfigUsers.GMS_SERVICE_ID); private GMSClient gmsClient; static { - Log4jInit.setLevel("org.opencadc.ac", Level.INFO); + Log4jInit.setLevel("org.opencadc.gms", Level.INFO); } public GmsClientIntTest() @@ -137,7 +135,7 @@ public GmsClientIntTest() unknownUser = new User(); unknownUser.getIdentities().add(new X500Principal(unknownDN)); - unknownGroup = new Group(new GroupURI(ConfigUsers.AC_SERVICE_ID + "?foo")); + unknownGroup = new Group(new GroupURI(ConfigUsers.GMS_SERVICE_ID + "?foo")); log.info("serviceURI: " + serviceURI); this.gmsClient = new GMSClient(serviceURI); diff --git a/gms/src/intTest/java/org/opencadc/gms/GroupMapTest.java b/gms/src/intTest/java/org/opencadc/gms/GroupMapTest.java new file mode 100644 index 00000000..39603bab --- /dev/null +++ b/gms/src/intTest/java/org/opencadc/gms/GroupMapTest.java @@ -0,0 +1,111 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2026. (c) 2026. +* Government of Canada Gouvernement du Canada +* National Research Council Conseil national de recherches +* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 +* All rights reserved Tous droits réservés +* +* NRC disclaims any warranties, Le CNRC dénie toute garantie +* expressed, implied, or énoncée, implicite ou légale, +* statutory, of any kind with de quelque nature que ce +* respect to the software, soit, concernant le logiciel, +* including without limitation y compris sans restriction +* any warranty of merchantability toute garantie de valeur +* or fitness for a particular marchande ou de pertinence +* purpose. NRC shall not be pour un usage particulier. +* liable in any event for any Le CNRC ne pourra en aucun cas +* damages, whether direct or être tenu responsable de tout +* indirect, special or general, dommage, direct ou indirect, +* consequential or incidental, particulier ou général, +* arising from the use of the accessoire ou fortuit, résultant +* software. Neither the name de l'utilisation du logiciel. Ni +* of the National Research le nom du Conseil National de +* Council of Canada nor the Recherches du Canada ni les noms +* names of its contributors may de ses participants ne peuvent +* be used to endorse or promote être utilisés pour approuver ou +* products derived from this promouvoir les produits dérivés +* software without specific prior de ce logiciel sans autorisation +* written permission. préalable et particulière +* par écrit. +* +* This file is part of the Ce fichier fait partie du projet +* OpenCADC project. OpenCADC. +* +* OpenCADC is free software: OpenCADC est un logiciel libre ; +* you can redistribute it and/or vous pouvez le redistribuer ou le +* modify it under the terms of modifier suivant les termes de +* the GNU Affero General Public la “GNU Affero General Public +* License as published by the License” telle que publiée +* Free Software Foundation, par la Free Software Foundation +* either version 3 of the : soit la version 3 de cette +* License, or (at your option) licence, soit (à votre gré) +* any later version. toute version ultérieure. +* +* OpenCADC is distributed in the OpenCADC est distribué +* hope that it will be useful, dans l’espoir qu’il vous +* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE +* without even the implied GARANTIE : sans même la garantie +* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ +* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF +* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence +* General Public License for Générale Publique GNU Affero +* more details. pour plus de détails. +* +* You should have received Vous devriez avoir reçu une +* a copy of the GNU Affero copie de la Licence Générale +* General Public License along Publique GNU Affero avec +* with OpenCADC. If not, see OpenCADC ; si ce n’est +* . pas le cas, consultez : +* . +* +************************************************************************ +*/ + +package org.opencadc.gms; + +import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.util.Log4jInit; +import java.net.URI; +import java.security.PrivilegedExceptionAction; +import java.util.Iterator; +import javax.security.auth.Subject; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; +import org.opencadc.auth.PosixGroup; +import org.opencadc.auth.PosixMapperClient; + +/** + * + * @author pdowler + */ +public class GroupMapTest { + private static final Logger log = Logger.getLogger(GroupMapTest.class); + + static { + Log4jInit.setLevel("org.opencadc.gms", Level.INFO); + } + + @Test + public void testGroupMap() throws Exception { + RegistryClient reg = new RegistryClient(); + URI srv = URI.create(ConfigUsers.GMS_SERVICE_ID); + PosixMapperClient pmc = new PosixMapperClient(srv); + + Iterator iter = Subject.doAs(ConfigUsers.getInstance().getOwnerSubject(), + (PrivilegedExceptionAction>) () -> pmc.getGroupMap()); + + Assert.assertNotNull(iter); + Assert.assertTrue(iter.hasNext()); + log.info("obtained uidmap:"); + while (iter.hasNext()) { + PosixGroup pg = iter.next(); + log.info(pg.getGroupURI() + " aka " + pg.getGID()); + } + } +} diff --git a/ac/src/intTest/java/org/opencadc/ac/GroupNamePrefixIntTest.java b/gms/src/intTest/java/org/opencadc/gms/GroupNamePrefixIntTest.java similarity index 95% rename from ac/src/intTest/java/org/opencadc/ac/GroupNamePrefixIntTest.java rename to gms/src/intTest/java/org/opencadc/gms/GroupNamePrefixIntTest.java index 2bac7bfe..11a59c77 100644 --- a/ac/src/intTest/java/org/opencadc/ac/GroupNamePrefixIntTest.java +++ b/gms/src/intTest/java/org/opencadc/gms/GroupNamePrefixIntTest.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2025. (c) 2025. +* (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -67,13 +67,12 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.ac.Group; import ca.nrc.cadc.ac.GroupNotFoundException; import ca.nrc.cadc.ac.client.GMSClient; -import ca.nrc.cadc.ac.integration.ConfigUsers; import ca.nrc.cadc.util.Log4jInit; import java.net.URI; import java.security.AccessControlException; @@ -84,7 +83,6 @@ import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.junit.Test; -import org.opencadc.gms.GroupURI; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -98,7 +96,7 @@ public class GroupNamePrefixIntTest static { - Log4jInit.setLevel("org.opencadc.ac", Level.INFO); + Log4jInit.setLevel("org.opencadc.gms", Level.DEBUG); } @@ -109,7 +107,7 @@ public void testAllow() try { - g = new Group(new GroupURI(ConfigUsers.AC_SERVICE_ID + "?ALLOW-TEST-" + UUID.randomUUID().toString())); + g = new Group(new GroupURI(ConfigUsers.GMS_SERVICE_ID + "?ALLOW-TEST-" + UUID.randomUUID().toString())); Group g2 = createGroupAs(g, ConfigUsers.getInstance().getOwnerSubject()); assertNotNull(g2); @@ -144,7 +142,7 @@ public void testDeny() { try { - g = new Group(new GroupURI(ConfigUsers.AC_SERVICE_ID + "?ALLOW-TEST-" + g = new Group(new GroupURI(ConfigUsers.GMS_SERVICE_ID + "?ALLOW-TEST-" + UUID.randomUUID().toString())); createGroupAs(g, ConfigUsers.getInstance().getMemberSubject()); @@ -236,7 +234,7 @@ public Group run() throws Exception { Group group = null; if (delay) { - // add delay to compensate for + // add delay to compensate for // eventual consistency of // content in multiple ldap hosts // try it several times @@ -266,6 +264,6 @@ public Group run() throws Exception private GMSClient getGMSClient() { - return new GMSClient(URI.create(ConfigUsers.AC_SERVICE_ID)); + return new GMSClient(URI.create(ConfigUsers.GMS_SERVICE_ID)); } } diff --git a/ac/src/intTest/java/ca/nrc/cadc/ac/integration/MembershipTests.java b/gms/src/intTest/java/org/opencadc/gms/MembershipTests.java similarity index 94% rename from ac/src/intTest/java/ca/nrc/cadc/ac/integration/MembershipTests.java rename to gms/src/intTest/java/org/opencadc/gms/MembershipTests.java index 3abd9715..da47075e 100644 --- a/ac/src/intTest/java/ca/nrc/cadc/ac/integration/MembershipTests.java +++ b/gms/src/intTest/java/org/opencadc/gms/MembershipTests.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * - * (c) 2025. (c) 2025. + * (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -64,7 +64,7 @@ * ************************************************************************ */ -package ca.nrc.cadc.ac.integration; +package org.opencadc.gms; import ca.nrc.cadc.auth.AuthMethod; import ca.nrc.cadc.net.HttpGet; @@ -94,9 +94,9 @@ public class MembershipTests { URL searchURL; public MembershipTests() throws Exception { - Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO); + Log4jInit.setLevel("org.opencadc.gms", Level.DEBUG); RegistryClient rc = new RegistryClient(); - searchURL = rc.getServiceURL(URI.create(ConfigUsers.AC_SERVICE_ID), Standards.GMS_SEARCH_01, AuthMethod.CERT); + searchURL = rc.getServiceURL(URI.create(ConfigUsers.GMS_SERVICE_ID), Standards.GMS_SEARCH_10, AuthMethod.CERT); } @Test @@ -146,8 +146,7 @@ public Object run() throws Exception { } @Test - public void testGetMemberships() { - try { + public void testGetMemberships() throws Exception{ Subject.doAs(ConfigUsers.getInstance().getOwnerSubject(), new PrivilegedExceptionAction() { @Override public Object run() throws Exception { @@ -172,10 +171,6 @@ public Object run() throws Exception { return null; } }); - } catch (Throwable t) { - log.info("unexpected: " + t.getMessage(), t); - Assert.fail("unexpected: " + t.getMessage()); - } } } diff --git a/ac/src/main/java/org/opencadc/ac/AbstractAction.java b/gms/src/main/java/org/opencadc/gms/AbstractAction.java similarity index 99% rename from ac/src/main/java/org/opencadc/ac/AbstractAction.java rename to gms/src/main/java/org/opencadc/gms/AbstractAction.java index 76736584..7bd1e17c 100755 --- a/ac/src/main/java/org/opencadc/ac/AbstractAction.java +++ b/gms/src/main/java/org/opencadc/gms/AbstractAction.java @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.ac.server.GroupPersistence; import ca.nrc.cadc.ac.server.PluginFactory; diff --git a/ac/src/main/java/ca/nrc/cadc/ac/server/impl/ArchiveGroupDetailSelector.java b/gms/src/main/java/org/opencadc/gms/ArchiveGroupDetailSelector.java similarity index 98% rename from ac/src/main/java/ca/nrc/cadc/ac/server/impl/ArchiveGroupDetailSelector.java rename to gms/src/main/java/org/opencadc/gms/ArchiveGroupDetailSelector.java index f3231482..65658045 100644 --- a/ac/src/main/java/ca/nrc/cadc/ac/server/impl/ArchiveGroupDetailSelector.java +++ b/gms/src/main/java/org/opencadc/gms/ArchiveGroupDetailSelector.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2011. (c) 2011. +* (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -67,7 +67,7 @@ ************************************************************************ */ -package ca.nrc.cadc.ac.server.impl; +package org.opencadc.gms; import ca.nrc.cadc.ac.Group; diff --git a/ac/src/main/java/ca/nrc/cadc/ac/CADCGmsConsts.java b/gms/src/main/java/org/opencadc/gms/CADCGmsConsts.java similarity index 96% rename from ac/src/main/java/ca/nrc/cadc/ac/CADCGmsConsts.java rename to gms/src/main/java/org/opencadc/gms/CADCGmsConsts.java index 2d95934a..74a6c677 100755 --- a/ac/src/main/java/ca/nrc/cadc/ac/CADCGmsConsts.java +++ b/gms/src/main/java/org/opencadc/gms/CADCGmsConsts.java @@ -2,7 +2,7 @@ ************************************************************************ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** * - * (c) 2014. (c) 2014. + * (c) 2026. (c) 2026. * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits reserves @@ -25,7 +25,7 @@ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** ************************************************************************ */ -package ca.nrc.cadc.ac; +package org.opencadc.gms; public class CADCGmsConsts { diff --git a/ac/src/main/java/org/opencadc/ac/CreateAction.java b/gms/src/main/java/org/opencadc/gms/CreateAction.java similarity index 99% rename from ac/src/main/java/org/opencadc/ac/CreateAction.java rename to gms/src/main/java/org/opencadc/gms/CreateAction.java index fe0e47c2..ffbb62a6 100755 --- a/ac/src/main/java/org/opencadc/ac/CreateAction.java +++ b/gms/src/main/java/org/opencadc/gms/CreateAction.java @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.ac.Group; import ca.nrc.cadc.ac.GroupAlreadyExistsException; diff --git a/ac/src/main/java/org/opencadc/ac/DeleteAction.java b/gms/src/main/java/org/opencadc/gms/DeleteAction.java similarity index 98% rename from ac/src/main/java/org/opencadc/ac/DeleteAction.java rename to gms/src/main/java/org/opencadc/gms/DeleteAction.java index 77d44944..ab60afb7 100755 --- a/ac/src/main/java/org/opencadc/ac/DeleteAction.java +++ b/gms/src/main/java/org/opencadc/gms/DeleteAction.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * - * (c) 2014. (c) 2014. + * (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.ac.Group; import ca.nrc.cadc.ac.GroupNotFoundException; diff --git a/ac/src/main/java/org/opencadc/ac/GetAction.java b/gms/src/main/java/org/opencadc/gms/GetAction.java similarity index 99% rename from ac/src/main/java/org/opencadc/ac/GetAction.java rename to gms/src/main/java/org/opencadc/gms/GetAction.java index 6ac97987..b5b26370 100755 --- a/ac/src/main/java/org/opencadc/ac/GetAction.java +++ b/gms/src/main/java/org/opencadc/gms/GetAction.java @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.ac.Group; import ca.nrc.cadc.ac.GroupNotFoundException; diff --git a/ac/src/main/java/org/opencadc/ac/GetGroupMapAction.java b/gms/src/main/java/org/opencadc/gms/GetGroupMapAction.java similarity index 98% rename from ac/src/main/java/org/opencadc/ac/GetGroupMapAction.java rename to gms/src/main/java/org/opencadc/gms/GetGroupMapAction.java index bb5b409c..32b03f19 100644 --- a/ac/src/main/java/org/opencadc/ac/GetGroupMapAction.java +++ b/gms/src/main/java/org/opencadc/gms/GetGroupMapAction.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2023. (c) 2023. +* (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -65,9 +65,8 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; -import ca.nrc.cadc.ac.server.impl.GroupPersistenceImpl; import ca.nrc.cadc.net.HttpTransfer; import ca.nrc.cadc.reg.Standards; import ca.nrc.cadc.reg.client.LocalAuthority; diff --git a/ac/src/main/java/org/opencadc/ac/GroupContentHandler.java b/gms/src/main/java/org/opencadc/gms/GroupContentHandler.java similarity index 99% rename from ac/src/main/java/org/opencadc/ac/GroupContentHandler.java rename to gms/src/main/java/org/opencadc/gms/GroupContentHandler.java index af1ffcc1..9c030db0 100755 --- a/ac/src/main/java/org/opencadc/ac/GroupContentHandler.java +++ b/gms/src/main/java/org/opencadc/gms/GroupContentHandler.java @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.ac.ReaderException; import ca.nrc.cadc.ac.xml.GroupReader; diff --git a/ac/src/main/java/org/opencadc/ac/GroupLogInfo.java b/gms/src/main/java/org/opencadc/gms/GroupLogInfo.java similarity index 99% rename from ac/src/main/java/org/opencadc/ac/GroupLogInfo.java rename to gms/src/main/java/org/opencadc/gms/GroupLogInfo.java index fcf05bab..5df7925b 100755 --- a/ac/src/main/java/org/opencadc/ac/GroupLogInfo.java +++ b/gms/src/main/java/org/opencadc/gms/GroupLogInfo.java @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import java.util.List; diff --git a/ac/src/main/java/ca/nrc/cadc/ac/server/impl/GroupPersistenceImpl.java b/gms/src/main/java/org/opencadc/gms/GroupPersistenceImpl.java similarity index 97% rename from ac/src/main/java/ca/nrc/cadc/ac/server/impl/GroupPersistenceImpl.java rename to gms/src/main/java/org/opencadc/gms/GroupPersistenceImpl.java index 22ca0b35..767a462f 100644 --- a/ac/src/main/java/ca/nrc/cadc/ac/server/impl/GroupPersistenceImpl.java +++ b/gms/src/main/java/org/opencadc/gms/GroupPersistenceImpl.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2011. (c) 2011. +* (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -67,7 +67,7 @@ ************************************************************************ */ -package ca.nrc.cadc.ac.server.impl; +package org.opencadc.gms; import ca.nrc.cadc.ac.Group; import ca.nrc.cadc.ac.GroupAlreadyExistsException; @@ -107,9 +107,10 @@ public class GroupPersistenceImpl extends LdapGroupPersistence { private static final Logger log = Logger.getLogger(GroupPersistenceImpl.class); - public static final String CONFIG_FILE = "ac-group-names.properties"; + public static final String CONFIG_FILE = "gms.properties"; private static User ARCHIVE_GROUP_OWNER; + public static final String POOL_READONLY = "readOnly"; static { @@ -355,4 +356,8 @@ public String toString() } } + public String getPoolStatistics() throws TransientException { + return super.getPool(POOL_READONLY).getPoolStatistics(); + } + } diff --git a/ac/src/main/java/org/opencadc/ac/GroupsConfig.java b/gms/src/main/java/org/opencadc/gms/GroupsConfig.java similarity index 96% rename from ac/src/main/java/org/opencadc/ac/GroupsConfig.java rename to gms/src/main/java/org/opencadc/gms/GroupsConfig.java index d86217d4..41aac772 100755 --- a/ac/src/main/java/org/opencadc/ac/GroupsConfig.java +++ b/gms/src/main/java/org/opencadc/gms/GroupsConfig.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * - * (c) 2025. (c) 2025. + * (c) 2026. (c) 2026. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.util.MultiValuedProperties; import ca.nrc.cadc.util.PropertiesReader; @@ -82,7 +82,7 @@ public class GroupsConfig { private static final Logger log = Logger.getLogger(GroupsConfig.class); // config keys - private static final String GROUPS_KEY = "org.opencadc.ac"; + private static final String GROUPS_KEY = "org.opencadc.gms"; static final String RESOURCE_ID = GROUPS_KEY + ".resourceID"; private static final String READ_USERS = GROUPS_KEY + ".readUser"; @@ -91,7 +91,7 @@ public class GroupsConfig { private final URI resourceID; public GroupsConfig() { - PropertiesReader r = new PropertiesReader("ac.properties"); + PropertiesReader r = new PropertiesReader("gms.properties"); this.configProperties = r.getAllProperties(); List resourceIdProp = configProperties.getProperty(RESOURCE_ID); if (resourceIdProp.isEmpty()) { diff --git a/ac/src/main/java/org/opencadc/ac/InitGroupAction.java b/gms/src/main/java/org/opencadc/gms/InitGroupAction.java similarity index 99% rename from ac/src/main/java/org/opencadc/ac/InitGroupAction.java rename to gms/src/main/java/org/opencadc/gms/InitGroupAction.java index c491b57a..76f19332 100755 --- a/ac/src/main/java/org/opencadc/ac/InitGroupAction.java +++ b/gms/src/main/java/org/opencadc/gms/InitGroupAction.java @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.rest.InitAction; import org.apache.log4j.Logger; diff --git a/ac/src/main/java/org/opencadc/ac/ModifyAction.java b/gms/src/main/java/org/opencadc/gms/ModifyAction.java similarity index 99% rename from ac/src/main/java/org/opencadc/ac/ModifyAction.java rename to gms/src/main/java/org/opencadc/gms/ModifyAction.java index c595e75c..c177007e 100755 --- a/ac/src/main/java/org/opencadc/ac/ModifyAction.java +++ b/gms/src/main/java/org/opencadc/gms/ModifyAction.java @@ -67,7 +67,7 @@ ************************************************************************ */ -package org.opencadc.ac; +package org.opencadc.gms; import ca.nrc.cadc.ac.Group; import ca.nrc.cadc.ac.GroupNotFoundException; diff --git a/gms/src/main/java/org/opencadc/gms/ServiceAvailability.java b/gms/src/main/java/org/opencadc/gms/ServiceAvailability.java new file mode 100755 index 00000000..0a086e43 --- /dev/null +++ b/gms/src/main/java/org/opencadc/gms/ServiceAvailability.java @@ -0,0 +1,196 @@ +/* + ************************************************************************ + **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** + * + * (c) 2026. (c) 2026. + * National Research Council Conseil national de recherches + * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 + * All rights reserved Tous droits reserves + * + * NRC disclaims any warranties Le CNRC denie toute garantie + * expressed, implied, or statu- enoncee, implicite ou legale, + * tory, of any kind with respect de quelque nature que se soit, + * to the software, including concernant le logiciel, y com- + * without limitation any war- pris sans restriction toute + * ranty of merchantability or garantie de valeur marchande + * fitness for a particular pur- ou de pertinence pour un usage + * pose. NRC shall not be liable particulier. Le CNRC ne + * in any event for any damages, pourra en aucun cas etre tenu + * whether direct or indirect, responsable de tout dommage, + * special or general, consequen- direct ou indirect, particul- + * tial or incidental, arising ier ou general, accessoire ou + * from the use of the software. fortuit, resultant de l'utili- + * sation du logiciel. + * + **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** + ************************************************************************ + */ +package org.opencadc.gms; + +import ca.nrc.cadc.ac.Group; +import ca.nrc.cadc.ac.GroupNotFoundException; +import ca.nrc.cadc.ac.server.GroupPersistence; +import ca.nrc.cadc.ac.server.PluginFactory; +import ca.nrc.cadc.ac.server.ldap.LdapConfig; +import ca.nrc.cadc.ac.server.ldap.LdapConfig.SystemState; +import ca.nrc.cadc.auth.AuthMethod; +import ca.nrc.cadc.auth.AuthenticationUtil; +import ca.nrc.cadc.auth.HttpPrincipal; +import ca.nrc.cadc.auth.PrincipalExtractor; +import ca.nrc.cadc.auth.X509CertificateChain; +import ca.nrc.cadc.net.TransientException; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.reg.client.LocalAuthority; +import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.vosi.Availability; +import ca.nrc.cadc.vosi.AvailabilityPlugin; +import ca.nrc.cadc.vosi.avail.CheckCertificate; +import ca.nrc.cadc.vosi.avail.CheckException; +import ca.nrc.cadc.vosi.avail.CheckResource; +import ca.nrc.cadc.vosi.avail.CheckWebService; +import java.io.File; +import java.net.URI; +import java.net.URL; +import java.security.Principal; +import java.security.PrivilegedExceptionAction; +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.UUID; +import javax.security.auth.Subject; +import org.apache.log4j.Logger; + +public class ServiceAvailability implements AvailabilityPlugin { + private static final Logger log = Logger.getLogger(ServiceAvailability.class); + + private static final String CALLER_UID = "cadcregtest1"; // TODO configuration? + private static final File AAI_PEM_FILE = new File(System.getProperty("user.home") + "/.ssl/cadcproxy.pem"); + + @Override + public void setAppName(String appName) { + // no op + } + + @Override + public boolean heartbeat() { + return true; + } + + public Availability getStatus() { + String note = "service is accepting requests"; + try { + LdapConfig ldapConfig = LdapConfig.getLdapConfig(); + if (ldapConfig.getSystemState().equals(SystemState.OFFLINE)) { + note = "service of offline"; + return new Availability(false, note); + } + + checkLdap(); + + if (ldapConfig.getSystemState().equals(SystemState.READONLY)) { + note = "service is in read-only mode"; + return new Availability(false, note); + } + + note += "; " + getPoolStats(); + return new Availability(true, note); + + } catch (CheckException ce) { + // tests determined that the resource is not working + note = ce.getMessage(); + return new Availability(false, note); + } catch (Throwable t) { + log.error("test failed", t); + note = "test failed, reason: " + t; + return new Availability(false, note); + } + } + + @Override + public void setState(String string) { + // No state changes supported + } + + private String getPoolStats() throws TransientException { + PluginFactory factory = new PluginFactory(); + GroupPersistenceImpl upi = (GroupPersistenceImpl) factory.createGroupPersistence(); + return upi.getPoolStatistics(); + } + + private void checkLdap() throws Exception { + try { + // augment a subject + log.debug("Start check LDAP"); + RegistryClient reg = new RegistryClient(); + LocalAuthority localAuthority = new LocalAuthority(); + URI usersURI = null; + try { + usersURI = localAuthority.getServiceURI(Standards.UMS_USERS_01.toString()); + URL url = reg.getServiceURL(usersURI, Standards.VOSI_AVAILABILITY, AuthMethod.ANON); + if (url != null) { + CheckResource checkResource = new CheckWebService(url); + checkResource.check(); + } else { + log.debug("check skipped: " + usersURI + " does not provide " + Standards.VOSI_AVAILABILITY); + } + } catch (NoSuchElementException ex) { + log.debug("not configured: " + Standards.UMS_USERS_01); + } + + if (usersURI != null) { + if (AAI_PEM_FILE.exists() && AAI_PEM_FILE.canRead()) { + // check for a certificate needed to perform network A&A ops + CheckCertificate checkCert = new CheckCertificate(AAI_PEM_FILE); + checkCert.check(); + } else { + log.debug("AAI cert not found or unreadable"); + } + } + + Subject subject = AuthenticationUtil.getSubject(new PrincipalExtractor() { + public Set getPrincipals() { + Set ret = new HashSet(); + ret.add(new HttpPrincipal(CALLER_UID)); + return ret; + } + + public X509CertificateChain getCertificateChain() { + return null; + } + }); + log.debug("test subject: " + subject); + + // make one group query + Subject.doAs(subject, new PrivilegedExceptionAction() { + public Object run() throws Exception { + PluginFactory factory = new PluginFactory(); + GroupPersistence dao = factory.createGroupPersistence(); + +// try { +// //Group g = dao.getGroup(UUID.randomUUID().toString()); +// } catch (GroupNotFoundException ignore) { +// } + return null; + } + }); + + } catch (Exception ex) { + StringBuilder sb = new StringBuilder(); + // strip IllegalStateException + if (ex.getCause() == null) { + sb.append("LDAP test query failed - EXCEPTION: " + ex); + throw new CheckException(sb.toString()); + } + Throwable t = ex.getCause(); + sb.append("LDAP test query failed - CAUSE: " + t); + while (t.getCause() != null) { + t = t.getCause(); + sb.append("\nCAUSE: " + t); + } + throw new CheckException(sb.toString()); + } + + + + } +} diff --git a/gms/src/main/resources/PluginFactory.properties b/gms/src/main/resources/PluginFactory.properties new file mode 100644 index 00000000..0c15157a --- /dev/null +++ b/gms/src/main/resources/PluginFactory.properties @@ -0,0 +1,6 @@ +## commented out values are the defaults, shown as examples +## to customise behaviour, subclass the specified class and +## change the configuration here + +## GroupPersistence implementation +ca.nrc.cadc.ac.server.GroupPersistence = org.opencadc.gms.GroupPersistenceImpl \ No newline at end of file diff --git a/gms/src/main/webapp/WEB-INF/web.xml b/gms/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..e10efac0 --- /dev/null +++ b/gms/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,254 @@ + + + + + + gms_ws + + + index.html + + + + GroupMapServlet + ca.nrc.cadc.rest.RestServlet + + get + org.opencadc.gms.GetGroupMapAction + + + 3 + + + + GroupServlet + ca.nrc.cadc.rest.RestServlet + + init + org.opencadc.gms.InitGroupAction + + + get + org.opencadc.gms.GetAction + + + delete + org.opencadc.gms.DeleteAction + + + post + org.opencadc.gms.ModifyAction + + + put + org.opencadc.gms.CreateAction + + 2 + + + + + + ACSearchServlet + ca.nrc.cadc.uws.server.JobServlet + + get + ca.nrc.cadc.uws.web.SyncGetAction + + + post + ca.nrc.cadc.uws.web.SyncPostAction + + + ca.nrc.cadc.uws.web.SyncPostAction.execOnPOST + true + + + ca.nrc.cadc.uws.server.JobManager + ca.nrc.cadc.ac.server.web.ACSearchJobManager + + 2 + + + + + + + CapabilitiesServlet + ca.nrc.cadc.rest.RestServlet + + init + ca.nrc.cadc.vosi.CapInitAction + + + get + ca.nrc.cadc.vosi.CapGetAction + + + head + ca.nrc.cadc.vosi.CapHeadAction + + + input + /capabilities.xml + + 3 + + + + + AvailabilityServlet + ca.nrc.cadc.vosi.AvailabilityServlet + + ca.nrc.cadc.vosi.AvailabilityPlugin + org.opencadc.gms.ServiceAvailability + + 3 + + + + + logControl + ca.nrc.cadc.log.LogControlServlet + + logLevel + info + + + logLevelPackages + + ca.nrc.cadc.ac + org.opencadc.gms + ca.nrc.cadc.auth + ca.nrc.cadc.rest + ca.nrc.cadc.vosi + + + 1 + + + + GroupMapServlet + /gidmap/* + + + + GroupServlet + /groups/* + + + + ACSearchServlet + /search/* + + + + ACSearchServlet + /auth/search/* + + + + + CapabilitiesServlet + /capabilities + + + + + AvailabilityServlet + /availability + + + + + logControl + /logControl + + + + + Public security role + public + + + diff --git a/gms/src/main/webapp/capabilities.xml b/gms/src/main/webapp/capabilities.xml new file mode 100644 index 00000000..55a86822 --- /dev/null +++ b/gms/src/main/webapp/capabilities.xml @@ -0,0 +1,57 @@ + + + + + + https://replace.me.com/gms/capabilities + + + + + + https://replace.me.com/gms/availability + + + + + + https://replace.me.com/gms/logControl + + + + + + + https://replace.me.com/gms/groups + + + + + + + + + + + https://replace.me.com/gms/search + + + + + + + + + + https://replace.me.com/gms/gidmap + + + + + + + + diff --git a/gms/src/test/java/org/opencadc/gms/AbstractActionTest.java b/gms/src/test/java/org/opencadc/gms/AbstractActionTest.java new file mode 100644 index 00000000..ec8d649d --- /dev/null +++ b/gms/src/test/java/org/opencadc/gms/AbstractActionTest.java @@ -0,0 +1,599 @@ +/* + ************************************************************************ + ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* + ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** + * + * (c) 2026. (c) 2026. + * Government of Canada Gouvernement du Canada + * National Research Council Conseil national de recherches + * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 + * All rights reserved Tous droits réservés + * + * NRC disclaims any warranties, Le CNRC dénie toute garantie + * expressed, implied, or énoncée, implicite ou légale, + * statutory, of any kind with de quelque nature que ce + * respect to the software, soit, concernant le logiciel, + * including without limitation y compris sans restriction + * any warranty of merchantability toute garantie de valeur + * or fitness for a particular marchande ou de pertinence + * purpose. NRC shall not be pour un usage particulier. + * liable in any event for any Le CNRC ne pourra en aucun cas + * damages, whether direct or être tenu responsable de tout + * indirect, special or general, dommage, direct ou indirect, + * consequential or incidental, particulier ou général, + * arising from the use of the accessoire ou fortuit, résultant + * software. Neither the name de l'utilisation du logiciel. Ni + * of the National Research le nom du Conseil National de + * Council of Canada nor the Recherches du Canada ni les noms + * names of its contributors may de ses participants ne peuvent + * be used to endorse or promote être utilisés pour approuver ou + * products derived from this promouvoir les produits dérivés + * software without specific prior de ce logiciel sans autorisation + * written permission. préalable et particulière + * par écrit. + * + * This file is part of the Ce fichier fait partie du projet + * OpenCADC project. OpenCADC. + * + * OpenCADC is free software: OpenCADC est un logiciel libre ; + * you can redistribute it and/or vous pouvez le redistribuer ou le + * modify it under the terms of modifier suivant les termes de + * the GNU Affero General Public la “GNU Affero General Public + * License as published by the License” telle que publiée + * Free Software Foundation, par la Free Software Foundation + * either version 3 of the : soit la version 3 de cette + * License, or (at your option) licence, soit (à votre gré) + * any later version. toute version ultérieure. + * + * OpenCADC is distributed in the OpenCADC est distribué + * hope that it will be useful, dans l’espoir qu’il vous + * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE + * without even the implied GARANTIE : sans même la garantie + * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ + * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF + * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence + * General Public License for Générale Publique GNU Affero + * more details. pour plus de détails. + * + * You should have received Vous devriez avoir reçu une + * a copy of the GNU Affero copie de la Licence Générale + * General Public License along Publique GNU Affero avec + * with OpenCADC. If not, see OpenCADC ; si ce n’est + * . pas le cas, consultez : + * . + * + * $Revision: 4 $ + * + ************************************************************************ + */ + +package org.opencadc.gms; + +import ca.nrc.cadc.ac.Group; +import ca.nrc.cadc.ac.GroupAlreadyExistsException; +import ca.nrc.cadc.ac.GroupNotFoundException; +import ca.nrc.cadc.ac.UserNotFoundException; +import ca.nrc.cadc.ac.server.IdentityManagerImpl; +import ca.nrc.cadc.net.TransientException; +import ca.nrc.cadc.auth.HttpPrincipal; +import ca.nrc.cadc.auth.IdentityType; +import ca.nrc.cadc.util.FileUtil; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.reg.client.LocalAuthority; +import ca.nrc.cadc.rest.SyncInput; +import ca.nrc.cadc.util.Log4jInit; +import ca.nrc.cadc.util.PropertiesReader; +import java.io.File; +import java.net.URI; +import java.security.AccessControlException; +import java.security.PrivilegedExceptionAction; +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +public class AbstractActionTest { + private static final Logger log = Logger.getLogger(AbstractActionTest.class); + + private AbstractAction abstractAction; + + @BeforeClass + public static void beforeClass() throws Exception { + Log4jInit.setLevel("org.opencadc.gms", Level.INFO); + } + + @Before + public void setUp() { + + abstractAction = new AbstractAction() { + @Override + public void doAction() { + + } + }; + } + + @Test + public void testSetRequestInputValidPath() { + checkPath("/groupName/userMembers/memberName", "groupName", "memberName", IdentityType.USERNAME.getValue()); + checkPath("/groupName/userMembers/memberName?idType=HTTP", "groupName", "memberName", IdentityType.USERNAME.getValue()); + checkPath("/groupName/userMembers/cn=memberName?idType=X500", "groupName", "cn=memberName", IdentityType.X500.getValue()); + checkPath("/groupName/userMembers/123?idType=CADC", "groupName", "123", IdentityType.CADC.getValue()); + + checkPath("/groupName/groupMembers/memberGroup", "groupName", "memberGroup", null); + } + + private void checkPath(String path, String expectedGroupName, String expectedMemberName, String expectedUserIDType) { + abstractAction.setSyncInput(new SyncInput() { + @Override + public String getPath() { + return path.split("\\?")[0]; + } + + @Override + public String getParameter(String name) { + if (path.contains("?")) { + return path.split("\\?")[1].split("idType=")[1]; + } + return null; + } + }); + abstractAction.setRequestInput(); + AbstractAction.RequestInput requestInput = abstractAction.requestInput; + assertEquals(expectedGroupName, requestInput.groupName); + assertEquals(expectedMemberName, requestInput.memberName); + assertEquals(expectedUserIDType, requestInput.userIDType); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetRequestInputInvalidPath() { + abstractAction.setSyncInput(new SyncInput() { + @Override + public String getPath() { + return "/invalid/path/with/too/many/segments"; + } + + @Override + public String getParameter(String name) { + return null; + } + }); + + abstractAction.setRequestInput(); + } + + /** + * + * @author pdowler + */ + public static class GroupPersistenceImplTest + { + private static final Logger log = Logger.getLogger(GroupPersistenceImplTest.class); + + private static final String ALLOWED_USER_PREFIX = "ALLOWED_USER-"; + private static final String ALLOWED_GROUP_PREFIX = "ALLOWED_USER_AND_GROUP-"; + + private static HttpPrincipal allowedPrincipal; + private static Subject allowedSubject; + private static Group allowedGroup; + private static Subject deniedSubject; + private static String configPath; + + @BeforeClass + public static void beforeClass() throws Exception + { + Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO); + + File conf = FileUtil.getFileFromResource(GroupPersistenceImpl.CONFIG_FILE, GroupPersistenceImplTest.class); + + allowedPrincipal = new HttpPrincipal("allowed-user"); + allowedSubject = new Subject(); + allowedSubject.getPrincipals().add(allowedPrincipal); + + HttpPrincipal dp = new HttpPrincipal("denied-user"); + deniedSubject = new Subject(); + deniedSubject.getPrincipals().add(dp); + + System.setProperty(PropertiesReader.CONFIG_DIR_SYSTEM_PROPERTY, "build/resources/test/config"); + URI gmsServiceURI = URI.create(new PropertiesReader("ac.properties").getFirstPropertyValue("org.opencadc.ac.resourceID")); + + LocalAuthority localAuthority = new LocalAuthority(); + + allowedGroup = new Group(new GroupURI(gmsServiceURI + "?allowed-group")); + + configPath = conf.getParent(); // build.xml copies it into same place + log.info("config path: " + configPath); + System.setProperty(PropertiesReader.class.getName() + ".dir", configPath); + } + + @Test + @Ignore + public void testGetAuthUserAndGroup() + { + try + { + System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); + + GroupPersistenceImpl impl = new GroupPersistenceImpl(); + + GroupPersistenceImpl.AuthUserAndGroup auth; + + try + { + auth = impl.getAuthUserAndGroup(null); + fail("null group name should throw exception"); + } + catch (IllegalArgumentException expected) {} + + auth = impl.getAuthUserAndGroup("foo"); + assertNull("should be null for unknown group name", auth); + + auth = impl.getAuthUserAndGroup(ALLOWED_USER_PREFIX + "123"); + assertNotNull("should not be null", auth); + assertNotNull(auth.authUser); + assertEquals(auth.authUser, allowedPrincipal); + assertNull(auth.authGroup); + + auth = impl.getAuthUserAndGroup(ALLOWED_GROUP_PREFIX + "123"); + assertNotNull("should not be null", auth); + assertNotNull(auth.authUser); + assertEquals(auth.authUser, allowedPrincipal); + assertNotNull(auth.authGroup); + assertEquals(auth.authGroup, allowedGroup); + } + catch(Exception unexpected) + { + log.error("unexpected exception", unexpected); + fail("unexpected exception: " + unexpected); + } + } + + @Test + @Ignore + public void testValidateGroupWithUser() + { + try + { + System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); + + GroupPersistenceImpl impl = new GroupPersistenceImpl(); + + // non-matching group names + GroupPersistenceImpl.AuthUserAndGroup authUserAndGroup; + authUserAndGroup = impl.validateGroupName(allowedSubject, "some-group"); + assertNull("auth user is not null", authUserAndGroup); + + authUserAndGroup = impl.validateGroupName(deniedSubject, "some-group"); + assertNull("auth user is not null", authUserAndGroup); + + // matching DENY- + try + { + impl.validateGroupName(allowedSubject, "DENY-123"); + fail("denied group prefix should throw AccessControlException"); + } + catch(AccessControlException expected) + { + log.debug("caught expected exception: " + expected); + } + + try + { + impl.validateGroupName(deniedSubject, "deny-123"); + fail("denied group prefix should throw AccessControlException"); + } + catch(AccessControlException expected) + { + log.debug("caught expected exception: " + expected); + } + + try + { + impl.validateGroupName(deniedSubject, "Deny-123"); + fail("denied group prefix should throw AccessControlException"); + } + catch(AccessControlException expected) + { + log.debug("caught expected exception: " + expected); + } + + // matching ALLOWED_USER- + authUserAndGroup = impl.validateGroupName(allowedSubject, "ALLOWED_USER-123"); + assertNotNull("auth user is null", authUserAndGroup); + assertEquals("", new HttpPrincipal("allowed-user"), authUserAndGroup.authUser); + + + // denied user + impl = new GroupPersistenceImpl() + { + @Override + boolean isMember(String groupID) throws UserNotFoundException, GroupNotFoundException, TransientException + { + return true; + } + }; + + try + { + impl.validateGroupName(deniedSubject, "ALLOWED_USER-123"); + fail("denied subject should throw AccessControlException"); + } + catch(AccessControlException expected) + { + log.debug("caught expected exception: " + expected); + } + } + catch(Exception unexpected) + { + log.error("unexpected exception", unexpected); + fail("unexpected exception: " + unexpected); + } + } + + @Test + @Ignore + public void testValidateGroupWithGroup() + { + try + { + System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); + + // non-matching ALLOWED_USER_AND_GROUP- + GroupPersistenceImpl impl = new GroupPersistenceImpl() + { + @Override + boolean isMember(String groupID) + throws UserNotFoundException, GroupNotFoundException, TransientException + { + return false; + } + }; + + try + { + impl.validateGroupName(deniedSubject, "ALLOWED_USER_AND_GROUP-123"); + fail("subject not in group should throw exception"); + } + catch (AccessControlException expected) {} + + // matching ALLOWED_USER_AND_GROUP- + impl = new GroupPersistenceImpl() + { + @Override + boolean isMember(String groupID) + throws UserNotFoundException, GroupNotFoundException, TransientException + { + return true; + } + }; + + GroupPersistenceImpl.AuthUserAndGroup authUserAndGroup = + impl.validateGroupName(deniedSubject, "ALLOWED_USER_AND_GROUP-123"); + assertNotNull("auth user is null", authUserAndGroup); + assertEquals("", new HttpPrincipal("allowed-user"), authUserAndGroup.authUser); + } + catch(Exception unexpected) + { + log.error("unexpected exception", unexpected); + fail("unexpected exception: " + unexpected); + } + } + + @Test + @Ignore + public void testAddGroupWithoutReservedPrefix() + { + try + { + System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); + + final GroupPersistenceImpl impl = new GroupPersistenceImpl() + { + Group g; + + @Override + AuthUserAndGroup validateGroupName(Subject caller, String gname) + throws GroupAlreadyExistsException, UserNotFoundException, TransientException + { + return null; + } + + @Override + Group superAddGroup(final Group group) + throws GroupAlreadyExistsException, TransientException, + UserNotFoundException, GroupNotFoundException + { + return g = group; + } + + @Override + Group superGetGroup(final Group group) + throws GroupAlreadyExistsException, TransientException, + UserNotFoundException, GroupNotFoundException + { + return g; + } + }; + + LocalAuthority localAuthority = new LocalAuthority(); + URI gmsServiceURI = localAuthority.getServiceURI(Standards.GMS_GROUPS_01.toString()); + + final Group expected = new Group(new GroupURI(gmsServiceURI + "?foo")); + + Group actual = Subject.doAs(allowedSubject, new PrivilegedExceptionAction() + { + @Override + public Group run() throws Exception + { + impl.addGroup(expected); + return impl.superGetGroup(expected); + } + }); + + assertNotNull("group is null", actual); + assertEquals("groups do not match", expected, actual); + } + catch(Exception unexpected) + { + log.error("unexpected exception", unexpected); + fail("unexpected exception: " + unexpected); + } + } + + @Test + @Ignore + public void testAddGroupWithReservedPrefix() + { + try + { + System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); + + final IdentityManagerImpl mockIM = EasyMock.createMock(IdentityManagerImpl.class); + mockIM.augmentSubject(allowedSubject); + EasyMock.expectLastCall().once(); + EasyMock.replay(mockIM); + + final GroupPersistenceImpl impl = new GroupPersistenceImpl() + { + Group g; + + @Override + AuthUserAndGroup validateGroupName(Subject caller, String gname) + throws GroupAlreadyExistsException, UserNotFoundException, TransientException + { + AuthUserAndGroup auth = new AuthUserAndGroup(); + auth.authUser = allowedPrincipal; + return auth; + } + + @Override + Group superAddGroup(final Group group) + throws GroupAlreadyExistsException, TransientException, + UserNotFoundException, GroupNotFoundException + { + return g = group; + } + + @Override + Group superGetGroup(final Group group) + throws GroupAlreadyExistsException, TransientException, + UserNotFoundException, GroupNotFoundException + { + return g; + } + + @Override + IdentityManagerImpl getIdentityManager() + { + return mockIM; + } + }; + + LocalAuthority localAuthority = new LocalAuthority(); + URI gmsServiceURI = localAuthority.getServiceURI(Standards.GMS_GROUPS_01.toString()); + final Group expected = new Group(new GroupURI(URI.create(gmsServiceURI + "?ALLOWED_USER-123"))); + + Group actual = Subject.doAs(allowedSubject, new PrivilegedExceptionAction() + { + @Override + public Group run() throws Exception + { + impl.addGroup(expected); + return impl.superGetGroup(expected); + } + }); + + assertNotNull("group is null", actual); + assertEquals("groups do not match", expected, actual); + + EasyMock.verify(mockIM); + } + catch(Exception unexpected) + { + log.error("unexpected exception", unexpected); + fail("unexpected exception: " + unexpected); + } + } + + @Test + @Ignore + public void testAddGroupGroupAlreadyExistsException() + { + try + { + System.setProperty("java.naming.factory.initial", TestContextFactory.class.getName()); + + final IdentityManagerImpl mockIM = EasyMock.createMock(IdentityManagerImpl.class); + mockIM.augmentSubject(allowedSubject); + EasyMock.expectLastCall().once(); + EasyMock.replay(mockIM); + + final GroupPersistenceImpl impl = new GroupPersistenceImpl() + { + @Override + AuthUserAndGroup validateGroupName(Subject caller, String gname) + throws GroupAlreadyExistsException, UserNotFoundException, TransientException + { + AuthUserAndGroup auth = new AuthUserAndGroup(); + auth.authUser = allowedPrincipal; + return auth; + } + + @Override + Group superAddGroup(final Group group) + throws GroupAlreadyExistsException, TransientException, + UserNotFoundException, GroupNotFoundException + { + throw new GroupAlreadyExistsException("group exists"); + } + + @Override + IdentityManagerImpl getIdentityManager() + { + return mockIM; + } + }; + + LocalAuthority localAuthority = new LocalAuthority(); + URI gmsServiceURI = localAuthority.getServiceURI(Standards.GMS_GROUPS_01.toString()); + + final Group expected = new Group(new GroupURI(gmsServiceURI + "?ALLOWED_USER-123")); + + Subject.doAs(allowedSubject, new PrivilegedExceptionAction() + { + @Override + public Object run() throws Exception + { + try + { + impl.addGroup(expected); + fail("should have thrown GroupAlreadyExistsException"); + } + catch (GroupAlreadyExistsException e) + { + assertEquals("wrong message", "group exists", e.getMessage()); + } + return null; + } + }); + + EasyMock.verify(mockIM); + } + catch(Exception unexpected) + { + log.error("unexpected exception", unexpected); + fail("unexpected exception: " + unexpected); + } + } + + } +} diff --git a/gms/src/test/java/org/opencadc/gms/TestContext.java b/gms/src/test/java/org/opencadc/gms/TestContext.java new file mode 100644 index 00000000..8269fb65 --- /dev/null +++ b/gms/src/test/java/org/opencadc/gms/TestContext.java @@ -0,0 +1,265 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2026. (c) 2026. +* Government of Canada Gouvernement du Canada +* National Research Council Conseil national de recherches +* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 +* All rights reserved Tous droits réservés +* +* NRC disclaims any warranties, Le CNRC dénie toute garantie +* expressed, implied, or énoncée, implicite ou légale, +* statutory, of any kind with de quelque nature que ce +* respect to the software, soit, concernant le logiciel, +* including without limitation y compris sans restriction +* any warranty of merchantability toute garantie de valeur +* or fitness for a particular marchande ou de pertinence +* purpose. NRC shall not be pour un usage particulier. +* liable in any event for any Le CNRC ne pourra en aucun cas +* damages, whether direct or être tenu responsable de tout +* indirect, special or general, dommage, direct ou indirect, +* consequential or incidental, particulier ou général, +* arising from the use of the accessoire ou fortuit, résultant +* software. Neither the name de l'utilisation du logiciel. Ni +* of the National Research le nom du Conseil National de +* Council of Canada nor the Recherches du Canada ni les noms +* names of its contributors may de ses participants ne peuvent +* be used to endorse or promote être utilisés pour approuver ou +* products derived from this promouvoir les produits dérivés +* software without specific prior de ce logiciel sans autorisation +* written permission. préalable et particulière +* par écrit. +* +* This file is part of the Ce fichier fait partie du projet +* OpenCADC project. OpenCADC. +* +* OpenCADC is free software: OpenCADC est un logiciel libre ; +* you can redistribute it and/or vous pouvez le redistribuer ou le +* modify it under the terms of modifier suivant les termes de +* the GNU Affero General Public la “GNU Affero General Public +* License as published by the License” telle que publiée +* Free Software Foundation, par la Free Software Foundation +* either version 3 of the : soit la version 3 de cette +* License, or (at your option) licence, soit (à votre gré) +* any later version. toute version ultérieure. +* +* OpenCADC is distributed in the OpenCADC est distribué +* hope that it will be useful, dans l’espoir qu’il vous +* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE +* without even the implied GARANTIE : sans même la garantie +* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ +* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF +* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence +* General Public License for Générale Publique GNU Affero +* more details. pour plus de détails. +* +* You should have received Vous devriez avoir reçu une +* a copy of the GNU Affero copie de la Licence Générale +* General Public License along Publique GNU Affero avec +* with OpenCADC. If not, see OpenCADC ; si ce n’est +* . pas le cas, consultez : +* . +* +* $Revision: 5 $ +* +************************************************************************ +*/ + +package org.opencadc.gms; + +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +/** + * A Simple JNDI context to support testing. + */ +public class TestContext implements Context +{ + Map map = new HashMap(1); + + @Override + public Object lookup(String name) throws NamingException + { + return map.get(name); + } + + @Override + public void bind(String name, Object value) throws NamingException + { + map.put(name, value); + } + + @Override + public Object addToEnvironment(String arg0, Object arg1) + throws NamingException + { + return null; + } + + @Override + public void bind(Name arg0, Object arg1) throws NamingException + { + } + + @Override + public void close() throws NamingException + { + } + + @Override + public Name composeName(Name arg0, Name arg1) throws NamingException + { + return null; + } + + @Override + public String composeName(String arg0, String arg1) + throws NamingException + { + return null; + } + + @Override + public Context createSubcontext(Name arg0) throws NamingException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Context createSubcontext(String arg0) throws NamingException + { + return null; + } + + @Override + public void destroySubcontext(Name arg0) throws NamingException + { + } + + @Override + public void destroySubcontext(String arg0) throws NamingException + { + // TODO Auto-generated method stub + + } + + @Override + public Hashtable getEnvironment() throws NamingException + { + return null; + } + + @Override + public String getNameInNamespace() throws NamingException + { + return null; + } + + @Override + public NameParser getNameParser(Name arg0) throws NamingException + { + return null; + } + + @Override + public NameParser getNameParser(String arg0) throws NamingException + { + return null; + } + + @Override + public NamingEnumeration list(Name arg0) + throws NamingException + { + return null; + } + + @Override + public NamingEnumeration list(String arg0) + throws NamingException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public NamingEnumeration listBindings(Name arg0) + throws NamingException + { + return null; + } + + @Override + public NamingEnumeration listBindings(String arg0) + throws NamingException + { + return null; + } + + @Override + public Object lookup(Name arg0) throws NamingException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Object lookupLink(Name arg0) throws NamingException + { + return null; + } + + @Override + public Object lookupLink(String arg0) throws NamingException + { + return null; + } + + @Override + public void rebind(Name arg0, Object arg1) throws NamingException + { + } + + @Override + public void rebind(String arg0, Object arg1) throws NamingException + { + } + + @Override + public Object removeFromEnvironment(String arg0) throws NamingException + { + return null; + } + + @Override + public void rename(Name arg0, Name arg1) throws NamingException + { + } + + @Override + public void rename(String arg0, String arg1) throws NamingException + { + } + + @Override + public void unbind(Name arg0) throws NamingException + { + } + + @Override + public void unbind(String arg0) throws NamingException + { + } + +} \ No newline at end of file diff --git a/gms/src/test/java/org/opencadc/gms/TestContextFactory.java b/gms/src/test/java/org/opencadc/gms/TestContextFactory.java new file mode 100644 index 00000000..3815aeb1 --- /dev/null +++ b/gms/src/test/java/org/opencadc/gms/TestContextFactory.java @@ -0,0 +1,93 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2026. (c) 2026. +* Government of Canada Gouvernement du Canada +* National Research Council Conseil national de recherches +* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 +* All rights reserved Tous droits réservés +* +* NRC disclaims any warranties, Le CNRC dénie toute garantie +* expressed, implied, or énoncée, implicite ou légale, +* statutory, of any kind with de quelque nature que ce +* respect to the software, soit, concernant le logiciel, +* including without limitation y compris sans restriction +* any warranty of merchantability toute garantie de valeur +* or fitness for a particular marchande ou de pertinence +* purpose. NRC shall not be pour un usage particulier. +* liable in any event for any Le CNRC ne pourra en aucun cas +* damages, whether direct or être tenu responsable de tout +* indirect, special or general, dommage, direct ou indirect, +* consequential or incidental, particulier ou général, +* arising from the use of the accessoire ou fortuit, résultant +* software. Neither the name de l'utilisation du logiciel. Ni +* of the National Research le nom du Conseil National de +* Council of Canada nor the Recherches du Canada ni les noms +* names of its contributors may de ses participants ne peuvent +* be used to endorse or promote être utilisés pour approuver ou +* products derived from this promouvoir les produits dérivés +* software without specific prior de ce logiciel sans autorisation +* written permission. préalable et particulière +* par écrit. +* +* This file is part of the Ce fichier fait partie du projet +* OpenCADC project. OpenCADC. +* +* OpenCADC is free software: OpenCADC est un logiciel libre ; +* you can redistribute it and/or vous pouvez le redistribuer ou le +* modify it under the terms of modifier suivant les termes de +* the GNU Affero General Public la “GNU Affero General Public +* License as published by the License” telle que publiée +* Free Software Foundation, par la Free Software Foundation +* either version 3 of the : soit la version 3 de cette +* License, or (at your option) licence, soit (à votre gré) +* any later version. toute version ultérieure. +* +* OpenCADC is distributed in the OpenCADC est distribué +* hope that it will be useful, dans l’espoir qu’il vous +* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE +* without even the implied GARANTIE : sans même la garantie +* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ +* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF +* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence +* General Public License for Générale Publique GNU Affero +* more details. pour plus de détails. +* +* You should have received Vous devriez avoir reçu une +* a copy of the GNU Affero copie de la Licence Générale +* General Public License along Publique GNU Affero avec +* with OpenCADC. If not, see OpenCADC ; si ce n’est +* . pas le cas, consultez : +* . +* +* $Revision: 5 $ +* +************************************************************************ +*/ + +package org.opencadc.gms; + +import java.util.Hashtable; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.spi.InitialContextFactory; + +/** + * A simple JNDI context factory to support testing. + */ +public class TestContextFactory implements InitialContextFactory +{ + + public TestContextFactory() + { + } + + @Override + public Context getInitialContext(Hashtable environment) throws NamingException + { + return new TestContext(); + } + +} diff --git a/gms/src/test/resources/config/gms.properties b/gms/src/test/resources/config/gms.properties new file mode 100644 index 00000000..de388100 --- /dev/null +++ b/gms/src/test/resources/config/gms.properties @@ -0,0 +1,3 @@ +org.opencadc.gms.resourceID = ivo://opencadc.ca/gms +org.opencadc.gms.privilegedX500Principals = cn=privileged +org.opencadc.gms.privilegedHttpPrincipals = privileged