diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/GroupDetailSelector.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/GroupDetailSelector.java new file mode 100644 index 0000000000000000000000000000000000000000..6874d550ed26406ebbaae8cd9489f1a82b23f11a --- /dev/null +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/GroupDetailSelector.java @@ -0,0 +1,89 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2011. (c) 2011. +* 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 +* <http://www.gnu.org/licenses/>. pas le cas, consultez : +* <http://www.gnu.org/licenses/>. +* +* $Revision: 5 $ +* +************************************************************************ +*/ + +package ca.nrc.cadc.ac.server; + +import ca.nrc.cadc.ac.Group; +import ca.nrc.cadc.ac.Role; + +/** + * + * @author pdowler + */ +public interface GroupDetailSelector +{ + /** + * Check if group details should be filled in for the group when + * querying for role. + * @param g + * @param r + * @return true if group details should be filled + */ + boolean isDetailedSearch(Group g, Role r); +} diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java index affb8c52a77107c2bc02eb85189be23281fc0dcd..c62b9be959726135df6a9cd60b4bd42c397c760d 100755 --- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java @@ -94,7 +94,7 @@ import java.util.Set; public abstract class LdapDAO { private static final Logger logger = Logger.getLogger(LdapDAO.class); - + private LDAPConnection conn; LdapConfig config; @@ -234,7 +234,11 @@ public abstract class LdapDAO throws TransientException { logger.debug("Ldap result: " + code); - System.out.println("Ldap result: " + code); + + if (code == ResultCode.SUCCESS || code == ResultCode.NO_SUCH_OBJECT) + { + return; + } if (code == ResultCode.INSUFFICIENT_ACCESS_RIGHTS) { @@ -244,10 +248,6 @@ public abstract class LdapDAO { throw new AccessControlException("Invalid credentials "); } - else if ((code == ResultCode.SUCCESS) || (code == ResultCode.NO_SUCH_OBJECT)) - { - // all good. nothing to do - } else if (code == ResultCode.PARAM_ERROR) { throw new IllegalArgumentException("Error in Ldap parameters "); @@ -256,10 +256,12 @@ public abstract class LdapDAO { throw new TransientException("Connection problems "); } - else + else if (code == ResultCode.TIMEOUT || code == ResultCode.TIME_LIMIT_EXCEEDED) { - throw new RuntimeException("Ldap error (" + code.getName() + ")"); + throw new TransientException("ldap timeout"); } + + throw new RuntimeException("Ldap error (" + code.getName() + ")"); } } diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java index 71ddf32316e6de250ab5ef5b9a9c2afdb26657b6..1cee54aed419269958d481b45a0614ffb1180692 100755 --- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java @@ -68,6 +68,18 @@ */ package ca.nrc.cadc.ac.server.ldap; +import java.security.AccessControlException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.security.auth.x500.X500Principal; + +import org.apache.log4j.Logger; + import ca.nrc.cadc.ac.ActivatedGroup; import ca.nrc.cadc.ac.Group; import ca.nrc.cadc.ac.GroupAlreadyExistsException; @@ -75,21 +87,44 @@ import ca.nrc.cadc.ac.GroupNotFoundException; import ca.nrc.cadc.ac.Role; import ca.nrc.cadc.ac.User; import ca.nrc.cadc.ac.UserNotFoundException; +import ca.nrc.cadc.ac.server.GroupDetailSelector; import ca.nrc.cadc.net.TransientException; import ca.nrc.cadc.util.StringUtil; -import com.unboundid.ldap.sdk.*; -import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; -import org.apache.log4j.Logger; -import javax.security.auth.x500.X500Principal; -import java.security.AccessControlException; -import java.security.Principal; -import java.util.*; +import com.unboundid.ldap.sdk.AddRequest; +import com.unboundid.ldap.sdk.Attribute; +import com.unboundid.ldap.sdk.DN; +import com.unboundid.ldap.sdk.Filter; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPResult; +import com.unboundid.ldap.sdk.LDAPSearchException; +import com.unboundid.ldap.sdk.Modification; +import com.unboundid.ldap.sdk.ModificationType; +import com.unboundid.ldap.sdk.ModifyRequest; +import com.unboundid.ldap.sdk.ResultCode; +import com.unboundid.ldap.sdk.SearchRequest; +import com.unboundid.ldap.sdk.SearchResult; +import com.unboundid.ldap.sdk.SearchResultEntry; +import com.unboundid.ldap.sdk.SearchScope; +import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; public class LdapGroupDAO<T extends Principal> extends LdapDAO { private static final Logger logger = Logger.getLogger(LdapGroupDAO.class); - + + private static final String[] PUB_GROUP_ATTRS = new String[] + { + "entrydn", "cn" + }; + private static final String[] GROUP_ATTRS = new String[] + { + "entrydn", "cn", "nsaccountlock", "owner", "modifytimestamp", "description" + }; + private static final String[] GROUP_AND_MEMBER_ATTRS = new String[] + { + "entrydn", "cn", "nsaccountlock", "owner", "modifytimestamp", "description", "uniquemember" + }; + private LdapUserDAO<T> userPersist; public LdapGroupDAO(LdapConfig config, LdapUserDAO<T> userPersist) @@ -105,33 +140,33 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO /** * Persists a group. - * + * * @param group The group to create - * + * * @return created group - * - * @throws GroupAlreadyExistsException If a group with the same ID already + * + * @throws GroupAlreadyExistsException If a group with the same ID already * exists. * @throws TransientException If an temporary, unexpected problem occurred. * @throws UserNotFoundException If owner or a member not valid user. - * @throws GroupNotFoundException + * @throws GroupNotFoundException */ public Group addGroup(final Group group) throws GroupAlreadyExistsException, TransientException, - UserNotFoundException, AccessControlException, + UserNotFoundException, AccessControlException, GroupNotFoundException { if (group.getOwner() == null) { throw new IllegalArgumentException("Group owner must be specified"); } - + if (!group.getProperties().isEmpty()) { throw new UnsupportedOperationException( "Support for groups properties not available"); } - + if (!isCreatorOwner(group.getOwner())) { throw new AccessControlException("Group owner must be creator"); @@ -146,22 +181,22 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO } else { - + DN ownerDN = userPersist.getUserDN(group.getOwner()); - + // add group to groups tree - LDAPResult result = addGroup(getGroupDN(group.getID()), - group.getID(), ownerDN, - group.description, - group.getUserMembers(), + LDAPResult result = addGroup(getGroupDN(group.getID()), + group.getID(), ownerDN, + group.description, + group.getUserMembers(), group.getGroupMembers()); LdapDAO.checkLdapResult(result.getResultCode()); - + // add group to admin groups tree - result = addGroup(getAdminGroupDN(group.getID()), - group.getID(), ownerDN, - group.description, - group.getUserAdmins(), + result = addGroup(getAdminGroupDN(group.getID()), + group.getID(), ownerDN, + group.description, + group.getUserAdmins(), group.getGroupAdmins()); LdapDAO.checkLdapResult(result.getResultCode()); // AD: Search results sometimes come incomplete if @@ -182,24 +217,24 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO logger.debug("addGroup Exception: " + e, e); LdapDAO.checkLdapResult(e.getResultCode()); throw new RuntimeException("Unexpected LDAP exception", e); - } + } } - + private LDAPResult addGroup(final DN groupDN, final String groupID, - final DN ownerDN, final String description, - final Set<User<? extends Principal>> users, + final DN ownerDN, final String description, + final Set<User<? extends Principal>> users, final Set<Group> groups) - throws UserNotFoundException, LDAPException, TransientException, + throws UserNotFoundException, LDAPException, TransientException, AccessControlException, GroupNotFoundException { // add new group List<Attribute> attributes = new ArrayList<Attribute>(); - Attribute ownerAttribute = + Attribute ownerAttribute = new Attribute("owner", ownerDN.toNormalizedString()); attributes.add(ownerAttribute); attributes.add(new Attribute("objectClass", "groupofuniquenames")); attributes.add(new Attribute("cn", groupID)); - + if (StringUtil.hasText(description)) { attributes.add(new Attribute("description", description)); @@ -223,7 +258,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO } if (!members.isEmpty()) { - attributes.add(new Attribute("uniquemember", + attributes.add(new Attribute("uniquemember", (String[]) members.toArray(new String[members.size()]))); } @@ -232,10 +267,11 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO new ProxiedAuthorizationV2RequestControl( "dn:" + getSubjectDN().toNormalizedString())); + logger.debug("addGroup: " + groupDN); return getConnection().add(addRequest); } - - + + /** * Checks whether group name available for the user or already in use. * @param group @@ -244,7 +280,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO * @throws UserNotFoundException * @throws GroupNotFoundException * @throws TransientException - * @throws GroupAlreadyExistsException + * @throws GroupAlreadyExistsException */ private Group reactivateGroup(final Group group) throws AccessControlException, UserNotFoundException, @@ -252,22 +288,20 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO { try { - // check group name exists + // check group name exists Filter filter = Filter.createEqualityFilter("cn", group.getID()); - SearchRequest searchRequest = - new SearchRequest( - getGroupDN(group.getID()) - .toNormalizedString(), SearchScope.SUB, filter, + DN groupDN = getGroupDN(group.getID()); + SearchRequest searchRequest = + new SearchRequest(groupDN.toNormalizedString(), SearchScope.BASE, filter, new String[] {"nsaccountlock"}); searchRequest.addControl( - new ProxiedAuthorizationV2RequestControl("dn:" + + new ProxiedAuthorizationV2RequestControl("dn:" + getSubjectDN().toNormalizedString())); - SearchResultEntry searchResult = - getConnection().searchForEntry(searchRequest); - + SearchResultEntry searchResult = getConnection().searchForEntry(searchRequest); + if (searchResult == null) { return null; @@ -277,18 +311,18 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO { throw new GroupAlreadyExistsException("Group already exists " + group.getID()); } - - // activate group + + // activate group try { return modifyGroup(null, group, true); - } + } catch (GroupNotFoundException e) { throw new RuntimeException( "BUG: group to modify does not exist" + group.getID()); - } - } + } + } catch (LDAPException e) { logger.debug("reactivateGroup Exception: " + e, e); @@ -296,67 +330,72 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO throw new RuntimeException("Unexpected LDAP exception", e); } } - - + + /** * Get all group names. - * + * * @return A collection of strings - * + * * @throws TransientException If an temporary, unexpected problem occurred. */ - public Collection<String> getGroupNames() throws TransientException + public Collection<String> getGroupNames() + throws TransientException { try { - final Filter filter = Filter.createPresenceFilter("cn"); - final String [] attributes = new String[] {"cn", "nsaccountlock"}; - final Collection<String> groupNames = new ArrayList<String>(); - final long begin = System.currentTimeMillis(); + Filter filter = Filter.createPresenceFilter("cn"); + String [] attributes = new String[] {"cn", "nsaccountlock"}; + + SearchRequest searchRequest = + new SearchRequest(config.getGroupsDN(), + SearchScope.SUB, filter, attributes); - final SearchResult searchResult = - getConnection().search(new SearchResultListener() + SearchResult searchResult = null; + try + { + searchResult = getConnection().search(searchRequest); + } + catch (LDAPSearchException e) { - @Override - public void searchEntryReturned( - final SearchResultEntry searchEntry) + logger.debug("Could not find groups root", e); + LdapDAO.checkLdapResult(e.getResultCode()); + if (e.getResultCode() == ResultCode.NO_SUCH_OBJECT) { - groupNames.add(searchEntry.getAttributeValue("cn")); + throw new IllegalStateException("Could not find groups root"); } - @Override - public void searchReferenceReturned( - final SearchResultReference searchReference) - { - - } - }, config.getGroupsDN(), SearchScope.ONE, filter, attributes); + throw new IllegalStateException("unexpected failure", e); + } LdapDAO.checkLdapResult(searchResult.getResultCode()); - long end = System.currentTimeMillis(); + List<String> groupNames = new ArrayList<String>(); + for (SearchResultEntry next : searchResult.getSearchEntries()) + { + if (!next.hasAttribute("nsaccountlock")) + { + groupNames.add(next.getAttributeValue("cn")); + } + } - logger.info("<-- groupNames in " + ((new Long(end).doubleValue() - - new Long(begin).doubleValue()) - / 1000.0) + " seconds."); return groupNames; } catch (LDAPException e1) { - logger.debug("getGroupNames Exception: " + e1, e1); + logger.debug("getGroupNames Exception: " + e1, e1); LdapDAO.checkLdapResult(e1.getResultCode()); - throw new IllegalStateException("Unexpected exception: " - + e1.getMatchedDN(), e1); + throw new IllegalStateException("Unexpected exception: " + e1.getMatchedDN(), e1); } - + } /** - * Get the group with the given Group ID. - * + * Get the group with members. + * * @param groupID The Group unique ID. - * + * * @return A Group instance - * + * * @throws GroupNotFoundException If the group was not found. * @throws TransientException If an temporary, unexpected problem occurred. */ @@ -364,185 +403,96 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO throws GroupNotFoundException, TransientException, AccessControlException { - return getGroup(groupID, true); - } - - public Group getGroup(final String groupID, final boolean withMembers) - throws GroupNotFoundException, TransientException, - AccessControlException - { - Group group = getGroup(getGroupDN(groupID), groupID, true); - - Group adminGroup = getAdminGroup(getAdminGroupDN(groupID), groupID, - true); - + Group group = getGroup(getGroupDN(groupID), groupID, GROUP_AND_MEMBER_ATTRS); + + Group adminGroup = getGroup(getAdminGroupDN(groupID), null, GROUP_AND_MEMBER_ATTRS); + group.getGroupAdmins().addAll(adminGroup.getGroupMembers()); group.getUserAdmins().addAll(adminGroup.getUserMembers()); + return group; } - - private Group getGroup(final DN groupDN, final String groupID, - final boolean withMembers) - throws GroupNotFoundException, TransientException, - AccessControlException - { - String [] attributes = new String[] {"entrydn", "cn", "description", - "owner", "uniquemember", - "modifytimestamp", "nsaccountlock"}; - return getGroup(groupDN, groupID, withMembers, attributes); - } - - private Group getAdminGroup(final DN groupDN, final String groupID, - final boolean withMembers) - throws GroupNotFoundException, TransientException, - AccessControlException - { - String [] attributes = new String[] {"entrydn", "cn", "owner", - "uniquemember"}; - return getGroup(groupDN, groupID, withMembers, attributes); - } - private Group getGroup(final DN groupDN, final String groupID, - final boolean withMembers, final String[] attributes) - throws GroupNotFoundException, TransientException, + // groupID is here so exceptions and loggiong have plain groupID instead of DN + private Group getGroup(final DN groupDN, final String xgroupID, String[] attributes) + throws GroupNotFoundException, TransientException, AccessControlException { + logger.debug("getGroup: " + groupDN + " attrs: " + attributes.length); + String loggableGroupID = xgroupID; + if (loggableGroupID == null) + loggableGroupID = groupDN.toString(); // member or admin group: same name, internal tree + try { - Filter filter = Filter.createEqualityFilter("cn", groupID); - - SearchRequest searchRequest = - new SearchRequest(groupDN.toNormalizedString(), - SearchScope.SUB, filter, attributes); + Filter filter = Filter.createNOTFilter(Filter.createPresenceFilter("nsaccountlock")); + + SearchRequest searchRequest = + new SearchRequest(groupDN.toNormalizedString(), + SearchScope.BASE, filter, attributes); searchRequest.addControl( - new ProxiedAuthorizationV2RequestControl("dn:" + + new ProxiedAuthorizationV2RequestControl("dn:" + getSubjectDN().toNormalizedString())); - SearchResult searchResult = null; - try - { - searchResult = getConnection().search(searchRequest); - } - catch (LDAPSearchException e) - { - if (e.getResultCode() == ResultCode.NO_SUCH_OBJECT) - { - String msg = "Group not found " + groupID; - logger.debug(msg); - throw new GroupNotFoundException(groupID); - } - else - { - LdapDAO.checkLdapResult(e.getResultCode()); - } - } - - if (searchResult.getEntryCount() == 0) - { - LdapDAO.checkLdapResult(searchResult.getResultCode()); - //access denied - String msg = "Not authorized to access " + groupID; - logger.debug(msg); - throw new AccessControlException(groupID); - } - - if (searchResult.getEntryCount() >1) - { - throw new RuntimeException("BUG: multiple results when retrieving group " + groupID); - } - - SearchResultEntry searchEntry = searchResult.getSearchEntries().get(0); - - if (searchEntry.getAttribute("nsaccountlock") != null) - { - // deleted group - String msg = "Group not found " + groupID; - logger.debug(msg); - throw new GroupNotFoundException(groupID); - } - - DN groupOwner = searchEntry.getAttributeValueAsDN("owner"); - if (groupOwner == null) - { - //TODO assume user not allowed to read group - throw new AccessControlException(groupID); - } - - User<X500Principal> owner; - try - { - owner = userPersist.getX500User(groupOwner); - } - catch (UserNotFoundException e) - { - throw new RuntimeException("BUG: group owner not found"); - } - - Group ldapGroup = new Group(groupID, owner); - if (searchEntry.hasAttribute("description")) - { - ldapGroup.description = - searchEntry.getAttributeValue("description"); - } - if (searchEntry.hasAttribute("modifytimestamp")) + + SearchResultEntry searchEntry = getConnection().searchForEntry(searchRequest); + + if (searchEntry == null) { - ldapGroup.lastModified = - searchEntry.getAttributeValueAsDate("modifytimestamp"); + String msg = "Group not found " + loggableGroupID; + logger.debug(msg + " cause: null"); + throw new GroupNotFoundException(loggableGroupID); } - if (withMembers) + Group ldapGroup = createGroupFromEntry(searchEntry, attributes); + + if (searchEntry.getAttributeValues("uniquemember") != null) { - if (searchEntry.getAttributeValues("uniquemember") != null) + for (String member : searchEntry.getAttributeValues("uniquemember")) { - for (String member : searchEntry - .getAttributeValues("uniquemember")) + DN memberDN = new DN(member); + if (memberDN.isDescendantOf(config.getUsersDN(), false)) { - DN memberDN = new DN(member); - if (memberDN.isDescendantOf(config.getUsersDN(), false)) + User<X500Principal> user; + try { - User<X500Principal> user; - try - { - user = userPersist.getX500User(memberDN); - } - catch (UserNotFoundException e) - { - throw new RuntimeException( - "BUG: group member not found"); - } + user = userPersist.getX500User(memberDN); ldapGroup.getUserMembers().add(user); } - else if (memberDN.isDescendantOf(config.getGroupsDN(), - false)) + catch (UserNotFoundException e) { - try - { - ldapGroup.getGroupMembers(). - add(new Group(getGroupID(memberDN))); - } - catch(GroupNotFoundException e) - { - // ignore as we are not cleaning up - // deleted groups from the group members - } + // ignore as we do not cleanup deleted users + // from groups they belong to } - else + } + else if (memberDN.isDescendantOf(config.getGroupsDN(), false)) + { + try + { + ldapGroup.getGroupMembers().add(getGroup(memberDN, null, PUB_GROUP_ATTRS)); + } + catch(GroupNotFoundException e) { - throw new RuntimeException( - "BUG: unknown member DN type: " + memberDN); + // ignore as we are not cleaning up + // deleted groups from the group members } } + else + { + throw new RuntimeException( + "BUG: unknown member DN type: " + memberDN); + } } } - + return ldapGroup; } catch (LDAPException e1) { - logger.debug("getGroup Exception: " + e1, e1); + logger.debug("getGroup Exception: " + e1, e1); LdapDAO.checkLdapResult(e1.getResultCode()); - throw new GroupNotFoundException("Not found " + groupID); + throw new RuntimeException("BUG: checkLdapResult didn't throw an exception"); } } @@ -550,9 +500,9 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO * Modify the given group. * * @param group The group to update. It must be an existing group - * + * * @return The newly updated group. - * + * * @throws GroupNotFoundException If the group was not found. * @throws TransientException If an temporary, unexpected problem occurred. * @throws AccessControlException If the operation is not permitted. @@ -563,9 +513,9 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO AccessControlException, UserNotFoundException { Group existing = getGroup(group.getID()); //group must exists first - return modifyGroup(existing, group, false); + return modifyGroup(existing, group, false); } - + private Group modifyGroup(final Group existing, final Group group, boolean withActivate) throws UserNotFoundException, TransientException, AccessControlException, GroupNotFoundException @@ -575,7 +525,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO throw new UnsupportedOperationException( "Support for groups properties not available"); } - + boolean adminChanges = false; List<Modification> mods = new ArrayList<Modification>(); @@ -595,90 +545,89 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO { mods.add(new Modification(ModificationType.REPLACE, "description", group.description)); } - - Set<String> newMembers = new HashSet<String>(); - for (User<?> member : group.getUserMembers()) - { - DN memberDN = userPersist.getUserDN(member); - newMembers.add(memberDN.toNormalizedString()); - } - for (Group gr : group.getGroupMembers()) + try { - if (!checkGroupExists(gr.getID())) + Set<String> newMembers = new HashSet<String>(); + for (User<?> member : group.getUserMembers()) { - throw new GroupNotFoundException(gr.getID()); + DN memberDN = userPersist.getUserDN(member); + newMembers.add(memberDN.toNormalizedString()); } - DN grDN = getGroupDN(gr.getID()); - newMembers.add(grDN.toNormalizedString()); - } - - Set<String> newAdmins = new HashSet<String>(); - Set<User<? extends Principal>> existingUserAdmins = new HashSet<User<? extends Principal>>(0); - if (existing != null) - { - existingUserAdmins = existing.getUserAdmins(); - } - for (User<?> member : group.getUserAdmins()) - { - DN memberDN = userPersist.getUserDN(member); - newAdmins.add(memberDN.toNormalizedString()); - if (!existingUserAdmins.contains(member)) + for (Group gr : group.getGroupMembers()) { - adminChanges = true; + if (!checkGroupExists(gr.getID())) + { + throw new GroupNotFoundException(gr.getID()); + } + DN grDN = getGroupDN(gr.getID()); + newMembers.add(grDN.toNormalizedString()); } - } - - Set<Group> existingGroupAdmins = new HashSet<Group>(0); - if (existing != null) - { - existingGroupAdmins = existing.getGroupAdmins(); - } - for (Group gr : group.getGroupAdmins()) - { - if (!checkGroupExists(gr.getID())) + + Set<String> newAdmins = new HashSet<String>(); + Set<User<? extends Principal>> existingUserAdmins = new HashSet<User<? extends Principal>>(0); + if (existing != null) { - throw new GroupNotFoundException(gr.getID()); + existingUserAdmins = existing.getUserAdmins(); + } + for (User<?> member : group.getUserAdmins()) + { + DN memberDN = userPersist.getUserDN(member); + newAdmins.add(memberDN.toNormalizedString()); + if (!existingUserAdmins.contains(member)) + { + adminChanges = true; + } } - DN grDN = getGroupDN(gr.getID()); - newAdmins.add(grDN.toNormalizedString()); - if (!existingGroupAdmins.contains(gr)) + Set<Group> existingGroupAdmins = new HashSet<Group>(0); + if (existing != null) { - adminChanges = true; + existingGroupAdmins = existing.getGroupAdmins(); + } + for (Group gr : group.getGroupAdmins()) + { + if (!checkGroupExists(gr.getID())) + { + throw new GroupNotFoundException(gr.getID()); + } + + DN grDN = getGroupDN(gr.getID()); + newAdmins.add(grDN.toNormalizedString()); + if (!existingGroupAdmins.contains(gr)) + { + adminChanges = true; + } + } + + mods.add(new Modification(ModificationType.REPLACE, "uniquemember", + (String[]) newMembers.toArray(new String[newMembers.size()]))); + adminMods.add(new Modification(ModificationType.REPLACE, "uniquemember", + (String[]) newAdmins.toArray(new String[newAdmins.size()]))); + + // modify admin group first (if necessary) + if (adminChanges) + { + ModifyRequest modifyRequest = new ModifyRequest(getAdminGroupDN(group.getID()), adminMods); + + modifyRequest.addControl( + new ProxiedAuthorizationV2RequestControl( + "dn:" + getSubjectDN().toNormalizedString())); + + LdapDAO.checkLdapResult(getConnection().modify(modifyRequest).getResultCode()); } - } - mods.add(new Modification(ModificationType.REPLACE, "uniquemember", - (String[]) newMembers.toArray(new String[newMembers.size()]))); - adminMods.add(new Modification(ModificationType.REPLACE, "uniquemember", - (String[]) newAdmins.toArray(new String[newAdmins.size()]))); - - try - { - // modify admin group first (if necessary) - if (adminChanges) - { - ModifyRequest modifyRequest = new ModifyRequest(getAdminGroupDN(group.getID()), adminMods); - - modifyRequest.addControl( - new ProxiedAuthorizationV2RequestControl( - "dn:" + getSubjectDN().toNormalizedString())); - LdapDAO.checkLdapResult(getConnection(). - modify(modifyRequest).getResultCode()); - } - // modify the group itself now - ModifyRequest modifyRequest = new ModifyRequest(getGroupDN(group.getID()), mods); + ModifyRequest modifyRequest = new ModifyRequest(getGroupDN(group.getID()), mods); modifyRequest.addControl( new ProxiedAuthorizationV2RequestControl( "dn:" + getSubjectDN().toNormalizedString())); - LdapDAO.checkLdapResult(getConnection(). - modify(modifyRequest).getResultCode()); + + LdapDAO.checkLdapResult(getConnection().modify(modifyRequest).getResultCode()); } catch (LDAPException e1) { - logger.debug("Modify Exception: " + e1, e1); + logger.debug("Modify Exception: " + e1, e1); LdapDAO.checkLdapResult(e1.getResultCode()); } try @@ -694,16 +643,15 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO } catch (GroupNotFoundException e) { - throw new RuntimeException( - "BUG: modified group not found (" + group.getID() + ")"); + throw new RuntimeException("BUG: modified group not found (" + group.getID() + ")"); } } /** * Deletes the group. - * + * * @param groupID The group to delete - * + * * @throws GroupNotFoundException If the group was not found. * @throws TransientException If an temporary, unexpected problem occurred. */ @@ -714,19 +662,19 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO deleteGroup(getGroupDN(groupID), groupID, false); deleteGroup(getAdminGroupDN(groupID), groupID, true); } - - private void deleteGroup(final DN groupDN, final String groupID, + + private void deleteGroup(final DN groupDN, final String groupID, final boolean isAdmin) throws GroupNotFoundException, TransientException, AccessControlException { - Group group = getGroup(groupDN, groupID, true); + Group group = getGroup(groupDN, groupID, GROUP_AND_MEMBER_ATTRS); List<Modification> modifs = new ArrayList<Modification>(); modifs.add(new Modification(ModificationType.ADD, "nsaccountlock", "true")); - + if (isAdmin) { - if (!group.getGroupAdmins().isEmpty() || + if (!group.getGroupAdmins().isEmpty() || !group.getUserAdmins().isEmpty()) { modifs.add(new Modification(ModificationType.DELETE, "uniquemember")); @@ -734,7 +682,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO } else { - if (!group.getGroupMembers().isEmpty() || + if (!group.getGroupMembers().isEmpty() || !group.getUserMembers().isEmpty()) { modifs.add(new Modification(ModificationType.DELETE, "uniquemember")); @@ -752,35 +700,32 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO } catch (LDAPException e1) { - logger.debug("Delete Exception: " + e1, e1); + logger.debug("Delete Exception: " + e1, e1); LdapDAO.checkLdapResult(e1.getResultCode()); } - + try { - getGroup(group.getID()); - throw new RuntimeException("BUG: group not deleted " + - group.getID()); + getGroup(getGroupDN(group.getID()), null, GROUP_ATTRS); + throw new RuntimeException("BUG: group not deleted " + group.getID()); } - catch (GroupNotFoundException ignore) {} + catch (GroupNotFoundException ignore) { } } - + /** - * Obtain a Collection of Groups that fit the given query. - * + * Obtain a Collection of Groups that fit the given query. The returned groups + * will not include members. + * * @param userID The userID. * @param role Role of the user, either owner, member, or read/write. * @param groupID The Group ID. - * - * @return Collection of Groups - * matching GROUP_READ_ACI.replace(ACTUAL_GROUP_TOKEN, - * readGrDN.toNormalizedString()) the query, or empty - * Collection. Never null. + * + * @return possibly empty collection of Group that match the query * @throws TransientException If an temporary, unexpected problem occurred. * @throws UserNotFoundException * @throws GroupNotFoundException */ - public Collection<Group> getGroups(final T userID, final Role role, + public Collection<Group> getGroups(final T userID, final Role role, final String groupID) throws TransientException, AccessControlException, GroupNotFoundException, UserNotFoundException @@ -796,107 +741,162 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO // no anonymous searches throw new AccessControlException("Not authorized to search"); } - - Collection<DN> groupDNs = new HashSet<DN>(); + + Collection<Group> ret; if (role == Role.OWNER) { - groupDNs.addAll(getOwnerGroups(user, userDN, groupID)); - } - else if (role == Role.MEMBER) - { - groupDNs.addAll(getMemberGroups(user, userDN, groupID, false)); - } - else if (role == Role.ADMIN) - { - groupDNs.addAll(getMemberGroups(user, userDN, groupID, true)); + ret = getOwnerGroups(user, userDN, groupID); } - - if (logger.isDebugEnabled()) + else { - for (DN dn : groupDNs) + Collection<DN> groupDNs = null; + + if (role == Role.MEMBER) { - logger.debug("Search adding DN: " + dn); + groupDNs = getMemberGroups(user, userDN, groupID, false); } - } - - Collection<Group> groups = new HashSet<Group>(); - try - { - for (DN groupDN : groupDNs) + else if (role == Role.ADMIN) { - if (role == Role.ADMIN) - { - groupDN = new DN(groupDN.getRDNString() + "," + config.getGroupsDN()); - } - try - { - groups.add(getGroup(groupDN)); - logger.debug("Search adding group: " + groupDN); - } - catch (GroupNotFoundException e) + groupDNs = getMemberGroups(user, userDN, groupID, true); + } + else + throw new IllegalArgumentException("null role"); + + ret = new ArrayList<Group>(); + try + { + for (DN groupDN : groupDNs) { - final String message = "BUG: group " + groupDN + " not found but " + - "membership exists (" + userID + ")"; - logger.error(message); - //throw new IllegalStateException(message); + if (role == Role.ADMIN) + { + groupDN = new DN(groupDN.getRDNString() + "," + config.getGroupsDN()); + } + try + { + Group g = createGroupFromDN(groupDN); + if (isDetailedSearch(g, role)) + { + g = getGroup(groupDN, null, GROUP_ATTRS); + } + logger.debug("found group: " + g.getID()); + ret.add(g); + } + catch (GroupNotFoundException e) + { + final String message = "BUG: group " + groupDN + " not found but " + + "membership exists (" + userID + ")"; + logger.error(message); + } } } + catch (LDAPException e) + { + logger.debug("getGroups Exception: " + e, e); + throw new TransientException("Error getting group", e); + } } - catch (LDAPException e) + + logger.debug("found: " + ret.size() + "groups matching " + userID + "," + role + "," + groupID); + return ret; + } + + // some pretty horrible hacks to avoid querying LDAP for group details... + private Group createGroupFromDN(DN groupDN) + { + String cn = groupDN.getRDNString(); + String[] parts = cn.split("="); + if (parts.length == 2 && parts[0].equals("cn")) { - logger.debug("getGroups Exception: " + e, e); - throw new TransientException("Error getting group", e); + return new Group(parts[1]); } - return groups; + throw new RuntimeException("BUG: failed to extract group name from " + groupDN.toString()); } - - protected Collection<DN> getOwnerGroups(final User<T> user, + // this gets filled by the LdapgroupPersistence + GroupDetailSelector searchDetailSelector; + + private boolean isDetailedSearch(Group g, Role r) + { + if (searchDetailSelector == null) + return true; + return searchDetailSelector.isDetailedSearch(g, r); + } + // end of horribleness + + protected Collection<Group> getOwnerGroups(final User<T> user, final DN userDN, final String groupID) - throws TransientException, AccessControlException, - GroupNotFoundException, UserNotFoundException + throws TransientException, AccessControlException { - Collection<DN> groupDNs = new HashSet<DN>(); + Collection<Group> ret = new ArrayList<Group>(); try - { - Filter filter = Filter.createEqualityFilter("owner", - userDN.toString()); + { + Filter filter = Filter.createNOTFilter(Filter.createPresenceFilter("nsaccountlock")); + + filter = Filter.createANDFilter(filter, + Filter.createEqualityFilter("owner", userDN.toNormalizedString())); + if (groupID != null) { - getGroup(groupID); - filter = Filter.createANDFilter(filter, - Filter.createEqualityFilter("cn", groupID)); + DN groupDN = getGroupDN(groupID); + filter = Filter.createANDFilter(filter, + Filter.createEqualityFilter("entrydn", groupDN.toNormalizedString())); } - + SearchRequest searchRequest = new SearchRequest( - config.getGroupsDN(), SearchScope.SUB, filter, "entrydn", "nsaccountlock"); - + config.getGroupsDN(), SearchScope.SUB, filter, GROUP_ATTRS); + searchRequest.addControl( - new ProxiedAuthorizationV2RequestControl("dn:" + + new ProxiedAuthorizationV2RequestControl("dn:" + getSubjectDN().toNormalizedString())); - + SearchResult results = getConnection().search(searchRequest); for (SearchResultEntry result : results.getSearchEntries()) { - String entryDN = result.getAttributeValue("entrydn"); - // make sure the group isn't deleted - if (result.getAttribute("nsaccountlock") == null) - { - groupDNs.add(new DN(entryDN)); - } - + ret.add(createGroupFromEntry(result, GROUP_ATTRS)); } } catch (LDAPException e1) { - logger.debug("getOwnerGroups Exception: " + e1, e1); + logger.debug("getOwnerGroups Exception: " + e1, e1); LdapDAO.checkLdapResult(e1.getResultCode()); } - return groupDNs; + return ret; + } + + private Group createGroupFromEntry(SearchResultEntry result, String[] attributes) + throws LDAPException + { + if (result.getAttribute("nsaccountlock") != null) + { + throw new RuntimeException("BUG: found group with nsaccountlock set: " + result.getAttributeValue("entrydn").toString()); + } + + String entryDN = result.getAttributeValue("entrydn"); + String groupName = result.getAttributeValue("cn"); + if (attributes == PUB_GROUP_ATTRS) + return new Group(groupName); + + DN ownerDN = result.getAttributeValueAsDN("owner"); + if (ownerDN == null) + throw new AccessControlException(groupName); + try + { + User owner = userPersist.getX500User(ownerDN); + Group g = new Group(groupName, owner); + if (result.hasAttribute("description")) + g.description = result.getAttributeValue("description"); + if (result.hasAttribute("modifytimestamp")) + g.lastModified = result.getAttributeValueAsDate("modifytimestamp"); + return g; + } + catch(UserNotFoundException ex) + { + throw new RuntimeException("Invalid state: owner does not exist: " + ownerDN + " group: " + entryDN); + } } - - protected Collection<DN> getMemberGroups(final User<T> user, - final DN userDN, + + protected Collection<DN> getMemberGroups(final User<T> user, + final DN userDN, final String groupID, final boolean isAdmin) throws TransientException, AccessControlException, @@ -922,114 +922,17 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO } else { - Collection<DN> memberGroupDNs = + Collection<DN> memberGroupDNs = userPersist.getUserGroups(user.getUserID(), isAdmin); groupDNs.addAll(memberGroupDNs); } return groupDNs; } - - /** - * Returns a group based on its LDAP DN. The returned group does not contain - * members or admins - * - * @param groupDN - * @return - * @throws com.unboundid.ldap.sdk.LDAPException - * @throws ca.nrc.cadc.ac.GroupNotFoundException - if group does not exist, - * it's deleted or caller has no access to it. - */ - protected Group getGroup(final DN groupDN) - throws LDAPException, GroupNotFoundException, UserNotFoundException - { - Filter filter = Filter.createEqualityFilter("entrydn", - groupDN.toNormalizedString()); - - SearchRequest searchRequest = new SearchRequest( - config.getGroupsDN(), SearchScope.SUB, filter, - "cn", "description", "owner", "nsaccountlock"); - - searchRequest.addControl( - new ProxiedAuthorizationV2RequestControl("dn:" + - getSubjectDN().toNormalizedString())); - - SearchResultEntry searchResult = - getConnection().searchForEntry(searchRequest); - if (searchResult == null) - { - String msg = "Group not found " + groupDN; - logger.debug(msg); - throw new GroupNotFoundException(groupDN.toNormalizedString()); - } - - if (searchResult.getAttribute("nsaccountlock") != null) - { - // deleted group - String msg = "Group not found " + groupDN; - logger.debug(msg); - throw new GroupNotFoundException(groupDN.toNormalizedString()); - } - - Group group = new Group(searchResult.getAttributeValue("cn"), - userPersist.getX500User( - new DN(searchResult.getAttributeValue( - "owner")))); - group.description = searchResult.getAttributeValue("description"); - return group; - } - - /** - * Returns a group ID corresponding to a DN. Although the groupID can be - * deduced from the group DN, this method checks if the group exists and - * it's active and throws an exception if any of those conditions are not - * met. - * - * @param groupDN - * @return - * @throws com.unboundid.ldap.sdk.LDAPException - * @throws ca.nrc.cadc.ac.GroupNotFoundException - Group not found or not - * active - */ - protected String getGroupID(final DN groupDN) - throws LDAPException, GroupNotFoundException - { - Filter filter = Filter.createEqualityFilter("entrydn", - groupDN.toNormalizedString()); - - SearchRequest searchRequest = new SearchRequest( - config.getGroupsDN(), SearchScope.SUB, filter, - "cn", "nsaccountlock"); - - searchRequest.addControl( - new ProxiedAuthorizationV2RequestControl("dn:" + - getSubjectDN().toNormalizedString())); - - SearchResultEntry searchResult = - getConnection().searchForEntry(searchRequest); - - if (searchResult == null) - { - String msg = "Group not found " + groupDN; - logger.debug(msg); - throw new GroupNotFoundException(groupDN.toNormalizedString()); - } - - if (searchResult.getAttribute("nsaccountlock") != null) - { - // deleted group - String msg = "Group not found " + groupDN; - logger.debug(msg); - throw new GroupNotFoundException(groupDN.toNormalizedString()); - } - - return searchResult.getAttributeValue("cn"); - } - /** - * + * * @param groupID - * @return + * @return */ protected DN getGroupDN(final String groupID) throws TransientException { @@ -1039,16 +942,16 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO } catch (LDAPException e) { - logger.debug("getGroupDN Exception: " + e, e); + logger.debug("getGroupDN Exception: " + e, e); LdapDAO.checkLdapResult(e.getResultCode()); } throw new IllegalArgumentException(groupID + " not a valid group ID"); } - + /** - * + * * @param groupID - * @return + * @return */ protected DN getAdminGroupDN(final String groupID) throws TransientException { @@ -1058,24 +961,24 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO } catch (LDAPException e) { - logger.debug("getAdminGroupDN Exception: " + e, e); + logger.debug("getAdminGroupDN Exception: " + e, e); LdapDAO.checkLdapResult(e.getResultCode()); } throw new IllegalArgumentException(groupID + " not a valid group ID"); } - + /** - * + * * @param owner * @return - * @throws UserNotFoundException + * @throws UserNotFoundException */ protected boolean isCreatorOwner(final User<? extends Principal> owner) throws UserNotFoundException { try { - User<X500Principal> subjectUser = + User<X500Principal> subjectUser = userPersist.getX500User(getSubjectDN()); if (subjectUser.equals(owner)) { @@ -1089,18 +992,20 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO throw new RuntimeException(e); } } - - private boolean checkGroupExists(String groupID) - throws TransientException + + private boolean checkGroupExists(String groupID) + throws LDAPException, TransientException { - for (String groupName : getGroupNames()) + try { - if (groupName.equalsIgnoreCase(groupID)) - { - return true; - } + Group g = getGroup(getGroupDN(groupID), groupID, PUB_GROUP_ATTRS); + return true; + } + catch(GroupNotFoundException ex) + { + return false; } - return false; + finally { } } } diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupPersistence.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupPersistence.java index e66dc2e5960f289be4050c3d87429b0a3f93c38d..9f66002c0eb4cd86c8e328440456b3f54408a585 100755 --- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupPersistence.java +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupPersistence.java @@ -79,6 +79,7 @@ import ca.nrc.cadc.ac.GroupAlreadyExistsException; import ca.nrc.cadc.ac.GroupNotFoundException; import ca.nrc.cadc.ac.Role; import ca.nrc.cadc.ac.UserNotFoundException; +import ca.nrc.cadc.ac.server.GroupDetailSelector; import ca.nrc.cadc.ac.server.GroupPersistence; import ca.nrc.cadc.net.TransientException; @@ -88,12 +89,19 @@ public class LdapGroupPersistence<T extends Principal> private static final Logger log = Logger.getLogger(LdapGroupPersistence.class); private final LdapConfig config; + + private GroupDetailSelector detailSelector; public LdapGroupPersistence() { config = LdapConfig.getLdapConfig(); } + protected void setDetailSelector(GroupDetailSelector gds) + { + this.detailSelector = gds; + } + public Collection<String> getGroupNames() throws TransientException, AccessControlException { @@ -233,6 +241,7 @@ public class LdapGroupPersistence<T extends Principal> { userDAO = new LdapUserDAO<T>(config); groupDAO = new LdapGroupDAO<T>(config, userDAO); + groupDAO.searchDetailSelector = detailSelector; Collection<Group> ret = groupDAO.getGroups(userID, role, groupID); return ret; } diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/ACSearchRunner.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/ACSearchRunner.java index f576ec6ba47de6cf4cff2ee3e8fb394d142e3a32..80661d4573cc2e4efaac40fcbdc5e609de09053d 100755 --- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/ACSearchRunner.java +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/ACSearchRunner.java @@ -335,6 +335,23 @@ public class ACSearchRunner implements JobRunner // } } */ + catch(IllegalArgumentException ex) + { + logInfo.setSuccess(true); + logInfo.setMessage(ex.getMessage()); + log.debug("FAIL", ex); + + syncOut.setResponseCode(400); + syncOut.setHeader("Content-Type", "text/plain"); + try + { + syncOut.getOutputStream().write(ex.getMessage().getBytes()); + } + catch (IOException e) + { + log.warn("Could not write response to output stream", e); + } + } catch (AccessControlException t) { logInfo.setSuccess(true); diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GroupServlet.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GroupServlet.java index 096b5827a25ad73b5efcb91427879d0c3cd28943..fb9a2dbfff49f2851412f52663898931a3d5b695 100755 --- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GroupServlet.java +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GroupServlet.java @@ -80,7 +80,6 @@ import org.apache.log4j.Logger; import ca.nrc.cadc.ac.server.web.SyncOutput; import ca.nrc.cadc.auth.AuthenticationUtil; -import ca.nrc.cadc.util.StringUtil; /** * Servlet for handling all requests to /groups diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java index b5f7b7fc706285aec7404148022f0a4ffae7f2a1..24b055459faa0ebe77d4fb669f3431118b147869 100644 --- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java +++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java @@ -494,11 +494,11 @@ public class LdapGroupDAOTest extends AbstractLdapDAOTest for (Group group : groups) { log.debug("admin group: " + group.getID()); - if (group.getID().equals(testGroup1ID)) + if (group.getID().equalsIgnoreCase(testGroup1ID)) { found1 = true; } - if (group.getID().equals(testGroup2ID)) + if (group.getID().equalsIgnoreCase(testGroup2ID)) { found2 = true; } @@ -861,18 +861,10 @@ public class LdapGroupDAOTest extends AbstractLdapDAOTest getGroupDAO().getGroups(unknownPrincipal, Role.OWNER, groupID); fail("searchGroups with unknown user should throw " + - "UserNotFoundException"); + "AccessControlException"); } catch (AccessControlException ignore) {} - try - { - getGroupDAO().getGroups(daoTestPrincipal1, Role.OWNER, - "foo"); - fail("searchGroups with unknown user should throw " + - "GroupNotFoundException"); - } - catch (GroupNotFoundException ignore) {} return null; } }); diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/Group.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/Group.java index 6a91450bc9acc1e27da28ae2f227edfa2fe8fd16..8dac1009e59bf92e492678dfb3837e7088aa0658 100644 --- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/Group.java +++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/Group.java @@ -155,6 +155,11 @@ public class Group return owner; } + public void setOwner(User<? extends Principal> owner) + { + this.owner = owner; + } + /** * * @return a set of properties associated with a group diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java index bad17d1f5f80cee819082265ca7f344940ac14b1..759e413e165e11ca1698eba743e2e5e6c2737212 100755 --- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java +++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java @@ -68,28 +68,6 @@ */ package ca.nrc.cadc.ac.client; -import ca.nrc.cadc.ac.Group; -import ca.nrc.cadc.ac.GroupAlreadyExistsException; -import ca.nrc.cadc.ac.GroupNotFoundException; -import ca.nrc.cadc.ac.Role; -import ca.nrc.cadc.ac.User; -import ca.nrc.cadc.ac.UserNotFoundException; -import ca.nrc.cadc.ac.xml.GroupListReader; -import ca.nrc.cadc.ac.xml.GroupReader; -import ca.nrc.cadc.ac.xml.GroupWriter; -import ca.nrc.cadc.auth.AuthenticationUtil; -import ca.nrc.cadc.auth.HttpPrincipal; -import ca.nrc.cadc.auth.SSLUtil; -import ca.nrc.cadc.net.HttpDownload; -import ca.nrc.cadc.net.HttpPost; -import ca.nrc.cadc.net.HttpUpload; -import ca.nrc.cadc.net.InputStreamWrapper; -import ca.nrc.cadc.net.NetUtil; -import org.apache.log4j.Logger; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; -import javax.security.auth.Subject; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -111,12 +89,36 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import javax.security.auth.Subject; + +import org.apache.log4j.Logger; + +import ca.nrc.cadc.ac.Group; +import ca.nrc.cadc.ac.GroupAlreadyExistsException; +import ca.nrc.cadc.ac.GroupNotFoundException; +import ca.nrc.cadc.ac.Role; +import ca.nrc.cadc.ac.UserNotFoundException; +import ca.nrc.cadc.ac.xml.GroupListReader; +import ca.nrc.cadc.ac.xml.GroupReader; +import ca.nrc.cadc.ac.xml.GroupWriter; +import ca.nrc.cadc.auth.AuthenticationUtil; +import ca.nrc.cadc.auth.SSLUtil; +import ca.nrc.cadc.net.HttpDownload; +import ca.nrc.cadc.net.HttpPost; +import ca.nrc.cadc.net.HttpUpload; +import ca.nrc.cadc.net.InputStreamWrapper; +import ca.nrc.cadc.net.NetUtil; +import ca.nrc.cadc.net.event.TransferEvent; +import ca.nrc.cadc.net.event.TransferListener; + /** * Client class for performing group searching and group actions * with the access control web service. */ -public class GMSClient +public class GMSClient implements TransferListener { private static final Logger log = Logger.getLogger(GMSClient.class); @@ -130,10 +132,10 @@ public class GMSClient * Constructor. * * @param baseURL The URL of the supporting access control web service - * obtained from the registry. + * obtained from the registry. */ - public GMSClient(final String baseURL) - throws IllegalArgumentException + public GMSClient(String baseURL) + throws IllegalArgumentException { if (baseURL == null) { @@ -159,71 +161,26 @@ public class GMSClient } } - /** - * Get a list of groups. - * - * @return The list of groups. - */ - public List<Group> getGroups() + public void transferEvent(TransferEvent te) { - throw new UnsupportedOperationException("Not yet implemented"); + if ( TransferEvent.RETRYING == te.getState() ) + log.debug("retry after request failed, reason: " + te.getError()); } + public String getEventHeader() + { + return null; // no custom eventID header + } /** - * Obtain all of the users as userID - name in JSON format. + * Get a list of groups. * - * @return List of HTTP Principal users. - * @throws IOException Any errors in reading. + * @return The list of groups. */ - public List<User<HttpPrincipal>> getDisplayUsers() throws IOException - { - final List<User<HttpPrincipal>> webUsers = - new ArrayList<User<HttpPrincipal>>(); - - final HttpDownload httpDownload = - createDisplayUsersHTTPDownload(webUsers); - - httpDownload.setRequestProperty("Accept", "application/json"); - httpDownload.run(); - - final Throwable error = httpDownload.getThrowable(); - - if (error != null) - { - final String errMessage = error.getMessage(); - final int responseCode = httpDownload.getResponseCode(); - - log.debug("getDisplayUsers response " + responseCode + ": " + - errMessage); - - if ((responseCode == 401) || (responseCode == 403) || - (responseCode == -1)) - { - throw new AccessControlException(errMessage); - } - else if (responseCode == 400) - { - throw new IllegalArgumentException(errMessage); - } - - throw new IOException("HttpResponse (" + responseCode + ") - " - + errMessage); - } - - log.debug("Content-Length: " + httpDownload.getContentLength()); - log.debug("Content-Type: " + httpDownload.getContentType()); - - return webUsers; - } - - HttpDownload createDisplayUsersHTTPDownload( - final List<User<HttpPrincipal>> webUsers) throws IOException + public List<Group> getGroups() { - final URL usersListURL = new URL(this.baseURL + "/users"); - return new HttpDownload(usersListURL, - new JsonUserListInputStreamWrapper(webUsers)); + throw new UnsupportedOperationException("Not yet implemented"); } /** @@ -233,13 +190,13 @@ public class GMSClient * @return The newly created group will all the information. * @throws GroupAlreadyExistsException If a group with the same name already * exists. - * @throws AccessControlException If unauthorized to perform this operation. + * @throws AccessControlException If unauthorized to perform this operation. * @throws UserNotFoundException * @throws IOException */ public Group createGroup(Group group) - throws GroupAlreadyExistsException, AccessControlException, - UserNotFoundException, IOException + throws GroupAlreadyExistsException, AccessControlException, + UserNotFoundException, IOException { URL createGroupURL = new URL(this.baseURL + "/groups"); log.debug("createGroupURL request to " + createGroupURL.toString()); @@ -310,7 +267,7 @@ public class GMSClient * @throws java.io.IOException */ public Group getGroup(String groupName) - throws GroupNotFoundException, AccessControlException, IOException + throws GroupNotFoundException, AccessControlException, IOException { URL getGroupURL = new URL(this.baseURL + "/groups/" + groupName); log.debug("getGroup request to " + getGroupURL.toString()); @@ -323,8 +280,7 @@ public class GMSClient Throwable error = transfer.getThrowable(); if (error != null) { - log.debug("getGroup throwable (" + transfer - .getResponseCode() + ")", error); + log.debug("getGroup throwable (" + transfer.getResponseCode() + ")", error); // transfer returns a -1 code for anonymous access. if ((transfer.getResponseCode() == -1) || (transfer.getResponseCode() == 401) || @@ -365,7 +321,7 @@ public class GMSClient * @throws java.io.IOException */ public List<String> getGroupNames() - throws AccessControlException, IOException + throws AccessControlException, IOException { final URL getGroupNamesURL = new URL(this.baseURL + "/groups"); log.debug("getGroupNames request to " + getGroupNamesURL.toString()); @@ -373,28 +329,26 @@ public class GMSClient final List<String> groupNames = new ArrayList<String>(); final HttpDownload httpDownload = new HttpDownload(getGroupNamesURL, new InputStreamWrapper() + { + @Override + public void read(final InputStream inputStream) throws IOException + { + try { - @Override - public void read(final InputStream inputStream) throws - IOException - { - try - { - InputStreamReader inReader = new InputStreamReader(inputStream); - BufferedReader reader = new BufferedReader(inReader); - String line; - while ((line = reader.readLine()) != null) - { - groupNames.add(line); - } - } - catch (Exception bug) - { - log.error("Unexpected exception", bug); - throw new RuntimeException(bug); - } + InputStreamReader inReader = new InputStreamReader(inputStream); + BufferedReader reader = new BufferedReader(inReader); + String line; + while ((line = reader.readLine()) != null) { + groupNames.add(line); } - }); + } + catch (Exception bug) + { + log.error("Unexpected exception", bug); + throw new RuntimeException(bug); + } + } + }); httpDownload.setSSLSocketFactory(getSSLSocketFactory()); httpDownload.run(); @@ -410,7 +364,7 @@ public class GMSClient errMessage); if ((responseCode == 401) || (responseCode == 403) || - (responseCode == -1)) + (responseCode == -1)) { throw new AccessControlException(errMessage); } @@ -433,15 +387,14 @@ public class GMSClient * @param group The update group object. * @return The group after update. * @throws IllegalArgumentException If cyclical membership is detected. - * @throws GroupNotFoundException If the group was not found. - * @throws UserNotFoundException If a member was not found. - * @throws AccessControlException If unauthorized to perform this operation. + * @throws GroupNotFoundException If the group was not found. + * @throws UserNotFoundException If a member was not found. + * @throws AccessControlException If unauthorized to perform this operation. * @throws java.io.IOException */ public Group updateGroup(Group group) - throws IllegalArgumentException, GroupNotFoundException, - UserNotFoundException, - AccessControlException, IOException + throws IllegalArgumentException, GroupNotFoundException, UserNotFoundException, + AccessControlException, IOException { URL updateGroupURL = new URL(this.baseURL + "/groups/" + group.getID()); log.debug("updateGroup request to " + updateGroupURL.toString()); @@ -457,8 +410,10 @@ public class GMSClient HttpPost transfer = new HttpPost(updateGroupURL, groupXML.toString(), "application/xml", true); transfer.setSSLSocketFactory(getSSLSocketFactory()); + transfer.setTransferListener(this); transfer.run(); + Throwable error = transfer.getThrowable(); if (error != null) { @@ -475,15 +430,10 @@ public class GMSClient } if (transfer.getResponseCode() == 404) { - if (error.getMessage() != null && error.getMessage() - .toLowerCase().contains("user")) - { + if (error.getMessage() != null && error.getMessage().toLowerCase().contains("user")) throw new UserNotFoundException(error.getMessage()); - } else - { throw new GroupNotFoundException(error.getMessage()); - } } throw new IOException(error); } @@ -511,7 +461,7 @@ public class GMSClient * @throws java.io.IOException */ public void deleteGroup(String groupName) - throws GroupNotFoundException, AccessControlException, IOException + throws GroupNotFoundException, AccessControlException, IOException { URL deleteGroupURL = new URL(this.baseURL + "/groups/" + groupName); log.debug("deleteGroup request to " + deleteGroupURL.toString()); @@ -536,7 +486,7 @@ public class GMSClient { responseCode = conn.getResponseCode(); } - catch (Exception e) + catch(Exception e) { throw new AccessControlException(e.getMessage()); } @@ -548,7 +498,7 @@ public class GMSClient errMessage); if ((responseCode == 401) || (responseCode == 403) || - (responseCode == -1)) + (responseCode == -1)) { throw new AccessControlException(errMessage); } @@ -570,13 +520,13 @@ public class GMSClient * @param targetGroupName The group in which to add the group member. * @param groupMemberName The group member to add. * @throws IllegalArgumentException If cyclical membership is detected. - * @throws GroupNotFoundException If the group was not found. - * @throws AccessControlException If unauthorized to perform this operation. + * @throws GroupNotFoundException If the group was not found. + * @throws AccessControlException If unauthorized to perform this operation. * @throws java.io.IOException */ public void addGroupMember(String targetGroupName, String groupMemberName) - throws IllegalArgumentException, GroupNotFoundException, - AccessControlException, IOException + throws IllegalArgumentException, GroupNotFoundException, + AccessControlException, IOException { URL addGroupMemberURL = new URL(this.baseURL + "/groups/" + targetGroupName + "/groupMembers/" + @@ -620,15 +570,14 @@ public class GMSClient * Add a user as a member of a group. * * @param targetGroupName The group in which to add the group member. - * @param userID The user to add. + * @param userID The user to add. * @throws GroupNotFoundException If the group was not found. - * @throws UserNotFoundException If the member was not found. + * @throws UserNotFoundException If the member was not found. * @throws java.io.IOException * @throws AccessControlException If unauthorized to perform this operation. */ public void addUserMember(String targetGroupName, Principal userID) - throws GroupNotFoundException, UserNotFoundException, - AccessControlException, IOException + throws GroupNotFoundException, UserNotFoundException, AccessControlException, IOException { String userIDType = AuthenticationUtil.getPrincipalType(userID); String encodedUserID = URLEncoder.encode(userID.getName(), "UTF-8"); @@ -665,15 +614,10 @@ public class GMSClient } if (responseCode == 404) { - if (errMessage != null && errMessage.toLowerCase() - .contains("user")) - { + if (errMessage != null && errMessage.toLowerCase().contains("user")) throw new UserNotFoundException(errMessage); - } else - { throw new GroupNotFoundException(errMessage); - } } throw new IOException(errMessage); } @@ -690,7 +634,7 @@ public class GMSClient */ public void removeGroupMember(String targetGroupName, String groupMemberName) - throws GroupNotFoundException, AccessControlException, IOException + throws GroupNotFoundException, AccessControlException, IOException { URL removeGroupMemberURL = new URL(this.baseURL + "/groups/" + targetGroupName + "/groupMembers/" + @@ -712,15 +656,13 @@ public class GMSClient .setSSLSocketFactory(getSSLSocketFactory()); } - // Try to handle anonymous access and throw AccessControlException + // Try to handle anonymous access and throw AccessControlException int responseCode = -1; try { responseCode = conn.getResponseCode(); } - catch (Exception ignore) - { - } + catch (Exception ignore) {} if (responseCode != 200) { @@ -750,15 +692,14 @@ public class GMSClient * Remove a user as a member of a group. * * @param targetGroupName The group from which to remove the group member. - * @param userID The user to remove. + * @param userID The user to remove. * @throws GroupNotFoundException If the group was not found. - * @throws UserNotFoundException If the member was not found. + * @throws UserNotFoundException If the member was not found. * @throws java.io.IOException * @throws AccessControlException If unauthorized to perform this operation. */ public void removeUserMember(String targetGroupName, Principal userID) - throws GroupNotFoundException, UserNotFoundException, - AccessControlException, IOException + throws GroupNotFoundException, UserNotFoundException, AccessControlException, IOException { String userIDType = AuthenticationUtil.getPrincipalType(userID); String encodedUserID = URLEncoder.encode(userID.toString(), "UTF-8"); @@ -784,15 +725,13 @@ public class GMSClient .setSSLSocketFactory(getSSLSocketFactory()); } - // Try to handle anonymous access and throw AccessControlException + // Try to handle anonymous access and throw AccessControlException int responseCode = -1; try { responseCode = conn.getResponseCode(); } - catch (Exception ignore) - { - } + catch (Exception ignore) {} if (responseCode != 200) { @@ -812,15 +751,10 @@ public class GMSClient } if (responseCode == 404) { - if (errMessage != null && errMessage.toLowerCase() - .contains("user")) - { + if (errMessage != null && errMessage.toLowerCase().contains("user")) throw new UserNotFoundException(errMessage); - } else - { throw new GroupNotFoundException(errMessage); - } } throw new IOException(errMessage); } @@ -830,22 +764,22 @@ public class GMSClient * Get all the memberships of the user of a certain role. * * @param userID Identifies the user. - * @param role The role to look up. + * @param role The role to look up. * @return A list of groups for which the user has the role. - * @throws UserNotFoundException If the user does not exist. - * @throws AccessControlException If not allowed to peform the search. + * @throws UserNotFoundException If the user does not exist. + * @throws AccessControlException If not allowed to peform the search. * @throws IllegalArgumentException If a parameter is null. - * @throws IOException If an unknown error occured. + * @throws IOException If an unknown error occured. */ public List<Group> getMemberships(Principal userID, Role role) - throws UserNotFoundException, AccessControlException, IOException + throws UserNotFoundException, AccessControlException, IOException { if (userID == null || role == null) { throw new IllegalArgumentException("userID and role are required."); } - List<Group> cachedGroups = getCachedGroups(userID, role); + List<Group> cachedGroups = getCachedGroups(userID, role, true); if (cachedGroups != null) { return cachedGroups; @@ -914,19 +848,19 @@ public class GMSClient * Return the group, specified by paramter groupName, if the user, * identified by userID, is a member of that group. Return null * otherwise. - * <p/> + * * This call is identical to getMemberShip(userID, groupName, Role.MEMBER) * - * @param userID Identifies the user. + * @param userID Identifies the user. * @param groupName Identifies the group. * @return The group or null of the user is not a member. - * @throws UserNotFoundException If the user does not exist. - * @throws AccessControlException If not allowed to peform the search. + * @throws UserNotFoundException If the user does not exist. + * @throws AccessControlException If not allowed to peform the search. * @throws IllegalArgumentException If a parameter is null. - * @throws IOException If an unknown error occured. + * @throws IOException If an unknown error occured. */ public Group getMembership(Principal userID, String groupName) - throws UserNotFoundException, AccessControlException, IOException + throws UserNotFoundException, AccessControlException, IOException { return getMembership(userID, groupName, Role.MEMBER); } @@ -936,35 +870,27 @@ public class GMSClient * identified by userID, is a member (of type role) of that group. * Return null otherwise. * - * @param userID Identifies the user. + * @param userID Identifies the user. * @param groupName Identifies the group. - * @param role The membership role to search. + * @param role The membership role to search. * @return The group or null of the user is not a member. - * @throws UserNotFoundException If the user does not exist. - * @throws AccessControlException If not allowed to peform the search. + * @throws UserNotFoundException If the user does not exist. + * @throws AccessControlException If not allowed to peform the search. * @throws IllegalArgumentException If a parameter is null. - * @throws IOException If an unknown error occured. + * @throws IOException If an unknown error occured. */ public Group getMembership(Principal userID, String groupName, Role role) - throws UserNotFoundException, AccessControlException, IOException + throws UserNotFoundException, AccessControlException, IOException { if (userID == null || groupName == null || role == null) { throw new IllegalArgumentException("userID and role are required."); } - List<Group> cachedGroups = getCachedGroups(userID, role); - if (cachedGroups != null) + Group cachedGroup = getCachedGroup(userID, groupName, role); + if (cachedGroup != null) { - int index = cachedGroups.indexOf(new Group(groupName)); - if (index != -1) - { - return cachedGroups.get(index); - } - else - { - return null; - } + return cachedGroup; } String idType = AuthenticationUtil.getPrincipalType(userID); @@ -1024,9 +950,9 @@ public class GMSClient } if (groups.size() == 1) { - // don't cache these results as it is not a complete - // list of memberships--it only applies to one group. - return groups.get(0); + Group ret = groups.get(0); + addCachedGroup(userID, ret, role); + return ret; } throw new IllegalStateException( "Duplicate membership for " + id + " in group " + groupName); @@ -1040,19 +966,19 @@ public class GMSClient /** * Check if userID is a member of groupName. - * <p/> + * * This is equivalent to isMember(userID, groupName, Role.MEMBER) * - * @param userID Identifies the user. + * @param userID Identifies the user. * @param groupName Identifies the group. * @return True if the user is a member of the group - * @throws UserNotFoundException If the user does not exist. - * @throws AccessControlException If not allowed to peform the search. + * @throws UserNotFoundException If the user does not exist. + * @throws AccessControlException If not allowed to peform the search. * @throws IllegalArgumentException If a parameter is null. - * @throws IOException If an unknown error occured. + * @throws IOException If an unknown error occured. */ public boolean isMember(Principal userID, String groupName) - throws UserNotFoundException, AccessControlException, IOException + throws UserNotFoundException, AccessControlException, IOException { return isMember(userID, groupName, Role.MEMBER); } @@ -1060,17 +986,17 @@ public class GMSClient /** * Check if userID is a member (of type role) of groupName. * - * @param userID Identifies the user. + * @param userID Identifies the user. * @param groupName Identifies the group. - * @param role The type of membership. + * @param role The type of membership. * @return True if the user is a member of the group - * @throws UserNotFoundException If the user does not exist. - * @throws AccessControlException If not allowed to peform the search. + * @throws UserNotFoundException If the user does not exist. + * @throws AccessControlException If not allowed to peform the search. * @throws IllegalArgumentException If a parameter is null. - * @throws IOException If an unknown error occured. + * @throws IOException If an unknown error occured. */ public boolean isMember(Principal userID, String groupName, Role role) - throws UserNotFoundException, AccessControlException, IOException + throws UserNotFoundException, AccessControlException, IOException { Group group = getMembership(userID, groupName, role); return group != null; @@ -1082,16 +1008,13 @@ public class GMSClient public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { if (mySocketFactory != null) - { throw new IllegalStateException("Illegal use of GMSClient: " - + "cannot set SSLSocketFactory after using one created from Subject"); - } + + "cannot set SSLSocketFactory after using one created from Subject"); this.sslSocketFactory = sslSocketFactory; clearCache(); } private int subjectHashCode = 0; - private SSLSocketFactory getSSLSocketFactory() { AccessControlContext ac = AccessController.getContext(); @@ -1114,12 +1037,9 @@ public class GMSClient { int c = s.hashCode(); if (c != subjectHashCode) - { throw new IllegalStateException("Illegal use of " - + this.getClass() - .getSimpleName() - + ": subject change not supported for internal SSLSocketFactory"); - } + + this.getClass().getSimpleName() + + ": subject change not supported for internal SSLSocketFactory"); } return this.mySocketFactory; } @@ -1128,15 +1048,13 @@ public class GMSClient { AccessControlContext acContext = AccessController.getContext(); Subject subject = Subject.getSubject(acContext); - if (subject != null) { - log.debug("Clearing cache"); - subject.getPrivateCredentials().clear(); + subject.getPrivateCredentials().remove(new GroupMemberships()); } } - protected List<Group> getCachedGroups(Principal userID, Role role) + protected GroupMemberships getGroupCache(Principal userID) { AccessControlContext acContext = AccessController.getContext(); Subject subject = Subject.getSubject(acContext); @@ -1144,46 +1062,81 @@ public class GMSClient // only consult cache if the userID is of the calling subject if (userIsSubject(userID, subject)) { - Set groupCredentialSet = subject - .getPrivateCredentials(GroupMemberships.class); - if ((groupCredentialSet != null) && - (groupCredentialSet.size() == 1)) + Set<GroupMemberships> gset = subject.getPrivateCredentials(GroupMemberships.class); + if (gset == null || gset.isEmpty()) { - Iterator i = groupCredentialSet.iterator(); - GroupMemberships groupMemberships = ((GroupMemberships) i - .next()); - return groupMemberships.memberships.get(role); + GroupMemberships mems = new GroupMemberships(); + subject.getPrivateCredentials().add(mems); + return mems; } + GroupMemberships mems = gset.iterator().next(); + return mems; + } + return null; // no cache + } + + protected Group getCachedGroup(Principal userID, String groupID, Role role) + { + List<Group> groups = getCachedGroups(userID, role, false); + if (groups == null) + return null; // no cache + for (Group g : groups) + { + if (g.getID().equals(groupID)) + return g; } return null; } + protected List<Group> getCachedGroups(Principal userID, Role role, boolean complete) + { + GroupMemberships mems = getGroupCache(userID); + if (mems == null) + return null; // no cache - protected void setCachedGroups(Principal userID, List<Group> groups, Role role) + Boolean cacheState = mems.complete.get(role); + if (!complete || Boolean.TRUE.equals(cacheState)) + return mems.memberships.get(role); + + // caller wanted complete and we don't have that + return null; + } + + protected void addCachedGroup(Principal userID, Group group, Role role) { - AccessControlContext acContext = AccessController.getContext(); - Subject subject = Subject.getSubject(acContext); + GroupMemberships mems = getGroupCache(userID); + if (mems == null) + return; // no cache - // only save to cache if the userID is of the calling subject - if (userIsSubject(userID, subject)) + List<Group> groups = mems.memberships.get(role); + if (groups == null) { - log.debug("Caching groups for " + userID + ", role " + role); + groups = new ArrayList<Group>(); + mems.complete.put(role, Boolean.FALSE); + mems.memberships.put(role, groups); + } + if (!groups.contains(group)) + groups.add(group); + } - final GroupMemberships groupCredentials; - Set groupCredentialSet = subject - .getPrivateCredentials(GroupMemberships.class); - if ((groupCredentialSet != null) && - (groupCredentialSet.size() == 1)) - { - Iterator i = groupCredentialSet.iterator(); - groupCredentials = ((GroupMemberships) i.next()); - } - else - { - groupCredentials = new GroupMemberships(); - subject.getPrivateCredentials().add(groupCredentials); - } + protected void setCachedGroups(Principal userID, List<Group> groups, Role role) + { + GroupMemberships mems = getGroupCache(userID); + if (mems == null) + return; // no cache - groupCredentials.memberships.put(role, groups); + log.debug("Caching groups for " + userID + ", role " + role); + List<Group> cur = mems.memberships.get(role); + if (cur == null) + { + cur = new ArrayList<Group>(); + mems.complete.put(role, Boolean.FALSE); + mems.memberships.put(role, cur); + } + for (Group group : groups) + { + if (!cur.contains(group)) + cur.add(group); + mems.complete.put(role, Boolean.TRUE); } } @@ -1196,7 +1149,7 @@ public class GMSClient for (Principal subjectPrincipal : subject.getPrincipals()) { - if (subjectPrincipal.equals(userID)) + if (AuthenticationUtil.equals(subjectPrincipal, userID)) { return true; } @@ -1205,17 +1158,31 @@ public class GMSClient } /** - * Class used to hold list of groups in which - * a user is a member. + * Class used to hold list of groups in which a user is known to be a member. */ - protected class GroupMemberships + protected class GroupMemberships implements Comparable { Map<Role, List<Group>> memberships = new HashMap<Role, List<Group>>(); + Map<Role, Boolean> complete = new HashMap<Role, Boolean>(); protected GroupMemberships() { } + // only allow one in a set - makes clearCache simple too + public boolean equals(Object rhs) + { + if (rhs != null && rhs instanceof GroupMemberships) + return true; + return false; + } + + public int compareTo(Object t) + { + if (this.equals(t)) + return 0; + return -1; // wonder if this is sketchy + } } } diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClientMain.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClientMain.java new file mode 100644 index 0000000000000000000000000000000000000000..e4a1fa6d6561729b0e67bfcffe8b46d894c9d4a2 --- /dev/null +++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClientMain.java @@ -0,0 +1,276 @@ +/* + ************************************************************************ + ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* + ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** + * + * (c) 2014. (c) 2014. + * 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 + * <http://www.gnu.org/licenses/>. pas le cas, consultez : + * <http://www.gnu.org/licenses/>. + * + * $Revision: 4 $ + * + ************************************************************************ + */ + +package ca.nrc.cadc.ac.client; + +import ca.nrc.cadc.ac.Group; +import ca.nrc.cadc.ac.User; +import java.net.URI; +import java.net.URL; +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import ca.nrc.cadc.auth.CertCmdArgUtil; +import ca.nrc.cadc.auth.HttpPrincipal; +import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.util.ArgumentMap; +import ca.nrc.cadc.util.Log4jInit; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.util.Set; +import javax.security.auth.x500.X500Principal; + +/** + * Prototype main class for the GMSClient. Currently + * only used for testing. Should not be used for production + * work. + */ +public class GMSClientMain implements PrivilegedAction<Object> +{ + + private static Logger log = Logger.getLogger(GMSClientMain.class); + + public static final String ARG_ADD_MEMBER = "add-member"; + public static final String ARG_CREATE_GROUP = "create"; + public static final String ARG_GET_GROUP = "get"; + public static final String ARG_DELETE_GROUP = "delete"; + + public static final String ARG_USERID = "userid"; + public static final String ARG_GROUP = "group"; + + public static final String ARG_HELP = "help"; + public static final String ARG_VERBOSE = "verbose"; + public static final String ARG_DEBUG = "debug"; + public static final String ARG_H = "h"; + public static final String ARG_V = "v"; + public static final String ARG_D = "d"; + + private GMSClient client; + private ArgumentMap argMap; + + private GMSClientMain() + { + RegistryClient regClient = new RegistryClient(); + URL acURL = null; + try + { + acURL = regClient.getServiceURL(new URI("ivo://cadc.nrc.ca/canfargms"), "https"); + } + catch (Exception e) + { + log.error("FAIL", e); + } + log.info("GMS service URL: " + acURL); + client = new GMSClient(acURL.toString()); + } + + public static void main(String[] args) + { + ArgumentMap argMap = new ArgumentMap(args); + + if (argMap.isSet(ARG_HELP) || argMap.isSet(ARG_H)) + { + usage(); + System.exit(0); + } + + // Set debug mode + if (argMap.isSet(ARG_DEBUG) || argMap.isSet(ARG_D)) + { + Log4jInit.setLevel("ca.nrc.cadc.ac.client", Level.DEBUG); + Log4jInit.setLevel("ca.nrc.cadc.net", Level.DEBUG); + } + else if (argMap.isSet(ARG_VERBOSE) || argMap.isSet(ARG_V)) + { + Log4jInit.setLevel("ca.nrc.cadc.ac.client", Level.INFO); + } + else + Log4jInit.setLevel("ca", Level.WARN); + + GMSClientMain main = new GMSClientMain(); + main.argMap = argMap; + + Subject subject = CertCmdArgUtil.initSubject(argMap, true); + + Object response = null; + + if (subject != null) + response = Subject.doAs(subject, main); + else + response = main.run(); + + log.debug("Response: " + response); + } + + private String getCommand() + { + if (argMap.isSet(ARG_ADD_MEMBER)) + return ARG_ADD_MEMBER; + + if (argMap.isSet(ARG_CREATE_GROUP)) + return ARG_CREATE_GROUP; + + if (argMap.isSet(ARG_GET_GROUP)) + return ARG_GET_GROUP; + + if (argMap.isSet(ARG_DELETE_GROUP)) + return ARG_DELETE_GROUP; + + throw new IllegalArgumentException("No valid commands"); + } + + private static void usage() + { + System.out.println("--add-member --group=<g> --userid=<u>"); + System.out.println("--create --group=<g>"); + System.out.println("--get --group=<g>"); + System.out.println("--delete --group=<g>"); + } + + @Override + public Object run() + { + try + { + String command = getCommand(); + + if (command.equals(ARG_ADD_MEMBER)) + { + String group = argMap.getValue(ARG_GROUP); + String userID = argMap.getValue(ARG_USERID); + + if (group == null) + throw new IllegalArgumentException("No group specified"); + + if (userID == null) + throw new IllegalArgumentException("No userid specified"); + + client.addUserMember(group, new HttpPrincipal(userID)); + } + else if (command.equals(ARG_CREATE_GROUP)) + { + String group = argMap.getValue(ARG_GROUP); + if (group == null) + throw new IllegalArgumentException("No group specified"); + + AccessControlContext accessControlContext = AccessController.getContext(); + Subject subject = Subject.getSubject(accessControlContext); + Set<X500Principal> principals = subject.getPrincipals(X500Principal.class); + X500Principal p = principals.iterator().next(); + + Group g = new Group(group, new User(p)); + g.getUserMembers().add(g.getOwner()); + client.createGroup(g); + } + else if (command.equals(ARG_GET_GROUP)) + { + String group = argMap.getValue(ARG_GROUP); + if (group == null) + throw new IllegalArgumentException("No group specified"); + + Group g = client.getGroup(group); + System.out.println("found: " + g.getID()); + System.out.println("\t" + g.description); + System.out.println("owner: " + g.getOwner()); + + for (User u : g.getUserAdmins()) + System.out.println("admin: " + u); + + for (Group ga : g.getGroupAdmins()) + System.out.println("admin: " + ga); + + for (User u : g.getUserMembers()) + System.out.println("member: " + u); + + for (Group gm : g.getGroupMembers()) + System.out.println("member: " + gm); + + } + else if (command.equals(ARG_DELETE_GROUP)) + { + String group = argMap.getValue(ARG_GROUP); + if (group == null) + throw new IllegalArgumentException("No group specified"); + + client.deleteGroup(group); + } + + return null; + } + catch (Throwable t) + { + log.error("ERROR", t); + return t; + } + } +} diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/GMSClientTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/GMSClientTest.java index 2da9b1e8948109ca172b198bf0e96965b54b6a7e..9bf89c7fff88d9ef792fbe0eaf873cc088a53e38 100644 --- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/GMSClientTest.java +++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/GMSClientTest.java @@ -97,48 +97,14 @@ import static org.easymock.EasyMock.*; public class GMSClientTest { - + private static final Logger log = Logger.getLogger(GMSClientTest.class); - + public GMSClientTest() { Log4jInit.setLevel("ca.nrc.cadc.ac", Level.DEBUG); } - - @Test - public void testGetDisplayUsers() throws Exception - { - final HttpDownload mockHTTPDownload = createMock(HttpDownload.class); - final GMSClient testSubject = new GMSClient("http://mysite.com/users") - { - @Override - HttpDownload createDisplayUsersHTTPDownload( - List<User<HttpPrincipal>> webUsers) throws IOException - { - return mockHTTPDownload; - } - }; - - mockHTTPDownload.setRequestProperty("Accept", "application/json"); - expectLastCall().once(); - - mockHTTPDownload.run(); - expectLastCall().once(); - - expect(mockHTTPDownload.getThrowable()).andReturn(null).once(); - - expect(mockHTTPDownload.getContentLength()).andReturn(88l).once(); - expect(mockHTTPDownload.getContentType()).andReturn( - "application/json").once(); - - replay(mockHTTPDownload); - - testSubject.getDisplayUsers(); - - verify(mockHTTPDownload); - } - @Test public void testUserIsSubject() { @@ -148,7 +114,7 @@ public class GMSClientTest HttpPrincipal userID = new HttpPrincipal("test"); HttpPrincipal userID2 = new HttpPrincipal("test2"); subject.getPrincipals().add(userID); - + RegistryClient regClient = new RegistryClient(); URL baseURL = regClient.getServiceURL(new URI(AC.GMS_SERVICE_URI), "https"); @@ -159,10 +125,10 @@ public class GMSClientTest Assert.assertFalse(client.userIsSubject(null, subject)); Assert.assertFalse(client.userIsSubject(userID2, subject)); Assert.assertTrue(client.userIsSubject(userID, subject)); - + HttpPrincipal userID3 = new HttpPrincipal("test3"); subject.getPrincipals().add(userID3); - + Assert.assertTrue(client.userIsSubject(userID, subject)); Assert.assertFalse(client.userIsSubject(userID2, subject)); Assert.assertTrue(client.userIsSubject(userID3, subject)); @@ -173,7 +139,7 @@ public class GMSClientTest Assert.fail("Unexpected exception: " + t.getMessage()); } } - + @Test public void testGroupCaching() { @@ -182,7 +148,7 @@ public class GMSClientTest Subject subject = new Subject(); final HttpPrincipal test1UserID = new HttpPrincipal("test"); subject.getPrincipals().add(test1UserID); - + RegistryClient regClient = new RegistryClient(); URL baseURL = regClient.getServiceURL(new URI(AC.GMS_SERVICE_URI), "https"); @@ -194,37 +160,48 @@ public class GMSClientTest public Object run() throws Exception { - List<Group> initial = client.getCachedGroups(test1UserID, Role.MEMBER); + List<Group> initial = client.getCachedGroups(test1UserID, Role.MEMBER, true); Assert.assertNull("Cache should be null", initial); + // add single group as isMember might do + Group group0 = new Group("0"); + client.addCachedGroup(test1UserID, group0, Role.MEMBER); + List<Group> actual = client.getCachedGroups(test1UserID, Role.MEMBER, true); + Assert.assertNull("Cache should be null", actual); + + Group g = client.getCachedGroup(test1UserID, "0", Role.MEMBER); + Assert.assertNotNull("cached group from incomplete cache", g); + + // add all groups like getMemberships might do List<Group> expected = new ArrayList<Group>(); Group group1 = new Group("1"); Group group2 = new Group("2"); + expected.add(group0); expected.add(group1); expected.add(group2); client.setCachedGroups(test1UserID, expected, Role.MEMBER); - List<Group> actual = client.getCachedGroups(test1UserID, Role.MEMBER); + actual = client.getCachedGroups(test1UserID, Role.MEMBER, true); Assert.assertEquals("Wrong cached groups", expected, actual); - + // check against another role - actual = client.getCachedGroups(test1UserID, Role.OWNER); + actual = client.getCachedGroups(test1UserID, Role.OWNER, true); Assert.assertNull("Cache should be null", actual); - + // check against another userid final HttpPrincipal anotherUserID = new HttpPrincipal("anotheruser"); - actual = client.getCachedGroups(anotherUserID, Role.MEMBER); + actual = client.getCachedGroups(anotherUserID, Role.MEMBER, true); Assert.assertNull("Cache should be null", actual); return null; } }); - + subject = new Subject(); final HttpPrincipal test2UserID = new HttpPrincipal("test2"); subject.getPrincipals().add(test2UserID); - + // do the same but as a different user Subject.doAs(subject, new PrivilegedExceptionAction<Object>() { @@ -232,7 +209,7 @@ public class GMSClientTest public Object run() throws Exception { - List<Group> initial = client.getCachedGroups(test2UserID, Role.MEMBER); + List<Group> initial = client.getCachedGroups(test2UserID, Role.MEMBER, true); Assert.assertNull("Cache should be null", initial); List<Group> expected = new ArrayList<Group>(); @@ -243,16 +220,16 @@ public class GMSClientTest client.setCachedGroups(test2UserID, expected, Role.MEMBER); - List<Group> actual = client.getCachedGroups(test2UserID, Role.MEMBER); + List<Group> actual = client.getCachedGroups(test2UserID, Role.MEMBER, true); Assert.assertEquals("Wrong cached groups", expected, actual); - + // check against another role - actual = client.getCachedGroups(test2UserID, Role.OWNER); + actual = client.getCachedGroups(test2UserID, Role.OWNER, true); Assert.assertNull("Cache should be null", actual); - + // check against another userid final HttpPrincipal anotherUserID = new HttpPrincipal("anotheruser"); - actual = client.getCachedGroups(anotherUserID, Role.MEMBER); + actual = client.getCachedGroups(anotherUserID, Role.MEMBER, true); Assert.assertNull("Cache should be null", actual); return null; @@ -261,7 +238,7 @@ public class GMSClientTest // do the same without a subject - List<Group> initial = client.getCachedGroups(test1UserID, Role.MEMBER); + List<Group> initial = client.getCachedGroups(test1UserID, Role.MEMBER, true); Assert.assertNull("Cache should be null", initial); List<Group> newgroups = new ArrayList<Group>(); @@ -272,7 +249,7 @@ public class GMSClientTest client.setCachedGroups(test1UserID, newgroups, Role.MEMBER); - List<Group> actual = client.getCachedGroups(test1UserID, Role.MEMBER); + List<Group> actual = client.getCachedGroups(test1UserID, Role.MEMBER, true); Assert.assertNull("Cache should still be null", actual); } catch (Throwable t)