/* ************************************************************************ ******************* 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 * . pas le cas, consultez : * . * * $Revision: 4 $ * ************************************************************************ */ package ca.nrc.cadc.ac.client; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.FileNotFoundException; import java.net.URI; import java.net.URL; import java.security.AccessControlContext; import java.security.AccessControlException; import java.security.AccessController; import java.security.Principal; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.net.ssl.SSLSocketFactory; import javax.security.auth.Subject; import ca.nrc.cadc.auth.AuthenticationUtil; import ca.nrc.cadc.auth.AuthMethod; import ca.nrc.cadc.auth.HttpPrincipal; import ca.nrc.cadc.auth.SSOCookieCredential; import ca.nrc.cadc.auth.X509CertificateChain; import ca.nrc.cadc.auth.SSLUtil; import ca.nrc.cadc.net.HttpDownload; import ca.nrc.cadc.net.HttpPost; import ca.nrc.cadc.net.HttpTransfer; import ca.nrc.cadc.net.HttpUpload; import ca.nrc.cadc.net.HttpDelete; import ca.nrc.cadc.net.NetUtil; import ca.nrc.cadc.net.InputStreamWrapper; 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.WriterException; 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.net.event.TransferEvent; import ca.nrc.cadc.net.event.TransferListener; import ca.nrc.cadc.reg.Standards; import ca.nrc.cadc.reg.client.RegistryClient; /** * Client class for performing group searching and group actions * with the access control web service. */ public class GMSClient implements TransferListener { private static final Logger log = Logger.getLogger(GMSClient.class); // socket factory to use when connecting private SSLSocketFactory sslSocketFactory; private SSLSocketFactory mySocketFactory; private URI serviceID; /** * Constructor. * * @param serviceID The service ID. */ public GMSClient(URI serviceID) { if (serviceID == null) throw new IllegalArgumentException("invalid serviceID: " + serviceID); if (serviceID.getFragment() != null) throw new IllegalArgumentException("invalid serviceID (fragment not allowed): " + serviceID); this.serviceID = serviceID; } public void transferEvent(TransferEvent te) { if ( TransferEvent.RETRYING == te.getState() ) log.debug("retry after request failed, reason: " + te.getError()); } public String getEventHeader() { return null; // no custom eventID header } /** * Get a list of groups. * * @return The list of groups. */ public List getGroups() { throw new UnsupportedOperationException("Not yet implemented"); } /** * Create a new group. * * @param group The group to create * @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 UserNotFoundException * @throws IOException */ public Group createGroup(Group group) throws GroupAlreadyExistsException, AccessControlException, UserNotFoundException, WriterException, IOException { URL createGroupURL = lookupServiceURL(Standards.GMS_GROUPS_01); log.debug("createGroupURL request to " + createGroupURL.toString()); // reset the state of the cache clearCache(); StringBuilder groupXML = new StringBuilder(); GroupWriter groupWriter = new GroupWriter(); groupWriter.write(group, groupXML); log.debug("createGroup: " + groupXML); byte[] bytes = groupXML.toString().getBytes("UTF-8"); ByteArrayInputStream in = new ByteArrayInputStream(bytes); HttpUpload transfer = new HttpUpload(in, createGroupURL); transfer.setSSLSocketFactory(getSSLSocketFactory()); transfer.run(); Throwable error = transfer.getThrowable(); if (error != null) { log.debug("createGroup throwable", error); // transfer returns a -1 code for anonymous uploads. if ((transfer.getResponseCode() == -1) || (transfer.getResponseCode() == 401) || (transfer.getResponseCode() == 403)) { throw new AccessControlException(error.getMessage()); } if (transfer.getResponseCode() == 400) { throw new IllegalArgumentException(error.getMessage()); } if (transfer.getResponseCode() == 409) { throw new GroupAlreadyExistsException(error.getMessage()); } if (transfer.getResponseCode() == 404) { throw new UserNotFoundException(error.getMessage()); } throw new IOException(error); } String retXML = transfer.getResponseBody(); try { log.debug("createGroup returned: " + retXML); GroupReader groupReader = new GroupReader(); return groupReader.read(retXML); } catch (Exception bug) { log.error("Unexpected exception", bug); throw new RuntimeException(bug); } } /** * Get the group object. * * @param groupName Identifies the group to get. * @return The group. * @throws GroupNotFoundException If the group was not found. * @throws AccessControlException If unauthorized to perform this operation. * @throws java.io.IOException */ public Group getGroup(String groupName) throws GroupNotFoundException, AccessControlException, IOException { URL groupsURL = lookupServiceURL(Standards.GMS_GROUPS_01); URL getGroupURL = new URL(groupsURL.toExternalForm() + "/" + groupName); log.debug("getGroup request to " + getGroupURL.toString()); ByteArrayOutputStream out = new ByteArrayOutputStream(); HttpDownload transfer = new HttpDownload(getGroupURL, out); transfer.setSSLSocketFactory(getSSLSocketFactory()); transfer.run(); Throwable error = transfer.getThrowable(); if (error != null) { log.debug("getGroup throwable (" + transfer.getResponseCode() + ")", error); // transfer returns a -1 code for anonymous access. if ((transfer.getResponseCode() == -1) || (transfer.getResponseCode() == 401) || (transfer.getResponseCode() == 403)) { throw new AccessControlException(error.getMessage()); } if (transfer.getResponseCode() == 400) { throw new IllegalArgumentException(error.getMessage()); } if (transfer.getResponseCode() == 404) { throw new GroupNotFoundException(error.getMessage()); } throw new IOException(error); } try { String groupXML = new String(out.toByteArray(), "UTF-8"); log.debug("getGroup returned: " + groupXML); GroupReader groupReader = new GroupReader(); return groupReader.read(groupXML); } catch (Exception bug) { log.error("Unexpected exception", bug); throw new RuntimeException(bug); } } /** * Get the all group names. * * @return The list of names. * @throws AccessControlException If unauthorized to perform this operation. * @throws java.io.IOException */ public List getGroupNames() throws AccessControlException, IOException { URL getGroupNamesURL = lookupServiceURL(Standards.GMS_GROUPS_01); log.debug("getGroupNames request to " + getGroupNamesURL.toString()); final List groupNames = new ArrayList<>(); final HttpDownload httpDownload = new HttpDownload(getGroupNamesURL, new InputStreamWrapper() { @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); } } }); // Disable retries. httpDownload.setRetry(0, 0, HttpTransfer.RetryReason.NONE); httpDownload.setSSLSocketFactory(getSSLSocketFactory()); httpDownload.run(); final Throwable error = httpDownload.getThrowable(); if (error != null) { final String errMessage = error.getMessage(); final int responseCode = httpDownload.getResponseCode(); log.debug("getGroupNames response " + responseCode + ": " + errMessage); if ((responseCode == 401) || (responseCode == 403) || (responseCode == -1)) { throw new AccessControlException(errMessage); } 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 groupNames; } /** * Update a group. * * @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 java.io.IOException */ public Group updateGroup(Group group) throws IllegalArgumentException, GroupNotFoundException, UserNotFoundException, AccessControlException, WriterException, IOException { URL groupsURL = lookupServiceURL(Standards.GMS_GROUPS_01); URL updateGroupURL = new URL(groupsURL.toExternalForm() + "/" + group.getID().getName()); log.debug("updateGroup request to " + updateGroupURL.toString()); // reset the state of the cache clearCache(); StringBuilder groupXML = new StringBuilder(); GroupWriter groupWriter = new GroupWriter(); groupWriter.write(group, groupXML); log.debug("updateGroup: " + groupXML); HttpPost transfer = new HttpPost(updateGroupURL, groupXML.toString(), "application/xml", false); transfer.setSSLSocketFactory(getSSLSocketFactory()); transfer.setTransferListener(this); transfer.run(); Throwable error = transfer.getThrowable(); if (error != null) { // transfer returns a -1 code for anonymous access. if ((transfer.getResponseCode() == -1) || (transfer.getResponseCode() == 401) || (transfer.getResponseCode() == 403)) { throw new AccessControlException(error.getMessage()); } if (transfer.getResponseCode() == 400) { throw new IllegalArgumentException(error.getMessage()); } if (transfer.getResponseCode() == 404) { if (error.getMessage() != null && error.getMessage().toLowerCase().contains("user")) throw new UserNotFoundException(error.getMessage()); else throw new GroupNotFoundException(error.getMessage()); } throw new IOException(error); } return getGroup(group.getID().getName()); } /** * Delete the group. * * @param groupName Identifies the group to delete. * @throws GroupNotFoundException If the group was not found. * @throws AccessControlException If unauthorized to perform this operation. * @throws java.io.IOException */ public void deleteGroup(String groupName) throws GroupNotFoundException, AccessControlException, IOException { URL groupsURL = lookupServiceURL(Standards.GMS_GROUPS_01); URL deleteGroupURL = new URL(groupsURL.toExternalForm() + "/" + groupName); log.debug("deleteGroup request to " + deleteGroupURL.toString()); // reset the state of the cache clearCache(); HttpDelete delete = new HttpDelete(deleteGroupURL, true); delete.setSSLSocketFactory(getSSLSocketFactory()); delete.run(); Throwable error = delete.getThrowable(); if (error != null) { // transfer returns a -1 code for anonymous access. if (error instanceof AccessControlException) { throw new AccessControlException(error.getMessage()); } if (delete.getResponseCode() == 400) { throw new IllegalArgumentException(error.getMessage()); } if (error instanceof FileNotFoundException) { throw new GroupNotFoundException(error.getMessage()); } throw new IOException(error); } } /** * Add a group as a member of another group. * * @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 java.io.IOException */ public void addGroupMember(String targetGroupName, String groupMemberName) throws IllegalArgumentException, GroupNotFoundException, AccessControlException, IOException { String path = "/" + targetGroupName + "/groupMembers/" + groupMemberName; URL groupsURL = lookupServiceURL(Standards.GMS_GROUPS_01); URL addGroupMemberURL = new URL(groupsURL.toExternalForm() + path); log.debug("addGroupMember request to " + addGroupMemberURL.toString()); // reset the state of the cache clearCache(); final InputStream is = new ByteArrayInputStream(new byte[0]); final HttpUpload httpUpload = new HttpUpload(is, addGroupMemberURL); httpUpload.setSSLSocketFactory(getSSLSocketFactory()); httpUpload.run(); final Throwable error = httpUpload.getThrowable(); if (error != null) { final int responseCode = httpUpload.getResponseCode(); final String errMessage = error.getMessage(); if ((responseCode == -1) || (responseCode == 401) || (responseCode == 403)) { throw new AccessControlException(errMessage); } if (responseCode == 400) { throw new IllegalArgumentException(errMessage); } if (responseCode == 404) { throw new GroupNotFoundException(errMessage); } throw new IOException(errMessage); } } /** * 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. * @throws GroupNotFoundException If the group 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 { if (targetGroupName == null) throw new IllegalArgumentException("targetGroupName required"); if (userID == null) throw new IllegalArgumentException("userID required"); log.debug("addUserMember: " + targetGroupName + " + " + userID.getName()); String userIDType = AuthenticationUtil.getPrincipalType(userID); String path = "/" + targetGroupName + "/userMembers/" + NetUtil.encode(userID.getName()) + "?idType=" + userIDType; URL groupsURL = lookupServiceURL(Standards.GMS_GROUPS_01); URL addUserMemberURL = new URL(groupsURL.toExternalForm() + path); log.debug("addUserMember request to " + addUserMemberURL.toString()); // reset the state of the cache clearCache(); final InputStream is = new ByteArrayInputStream(new byte[0]); final HttpUpload httpUpload = new HttpUpload(is, addUserMemberURL); httpUpload.setSSLSocketFactory(getSSLSocketFactory()); httpUpload.run(); final Throwable error = httpUpload.getThrowable(); if (error != null) { final int responseCode = httpUpload.getResponseCode(); final String errMessage = error.getMessage(); if ((responseCode == -1) || (responseCode == 401) || (responseCode == 403)) { throw new AccessControlException(errMessage); } if (responseCode == 400) { throw new IllegalArgumentException(errMessage); } if (responseCode == 404) { if (errMessage != null && errMessage.toLowerCase().contains("user")) throw new UserNotFoundException(errMessage); else throw new GroupNotFoundException(errMessage); } throw new IOException(errMessage); } } /** * Remove a group as a member of another group. * * @param targetGroupName The group from which to remove the group member. * @param groupMemberName The group member to remove. * @throws GroupNotFoundException If the group was not found. * @throws java.io.IOException * @throws AccessControlException If unauthorized to perform this operation. */ public void removeGroupMember(String targetGroupName, String groupMemberName) throws GroupNotFoundException, AccessControlException, IOException { String path = "/" + targetGroupName + "/groupMembers/" + groupMemberName; URL groupsURL = lookupServiceURL(Standards.GMS_GROUPS_01); URL removeGroupMemberURL = new URL(groupsURL.toExternalForm() + path); log.debug("removeGroupMember request to " + removeGroupMemberURL.toString()); // reset the state of the cache clearCache(); HttpDelete delete = new HttpDelete(removeGroupMemberURL, true); delete.setSSLSocketFactory(getSSLSocketFactory()); delete.run(); Throwable error = delete.getThrowable(); if (error != null) { // transfer returns a -1 code for anonymous access. if (error instanceof AccessControlException) { throw ((AccessControlException) error); } if (delete.getResponseCode() == 400) { throw new IllegalArgumentException(error.getMessage()); } if (error instanceof FileNotFoundException) { throw new GroupNotFoundException(error.getMessage()); } throw new IOException(error); } } /** * 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. * @throws GroupNotFoundException If the group 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 { String userIDType = AuthenticationUtil.getPrincipalType(userID); log.debug("removeUserMember: " + targetGroupName + " - " + userID.getName() + " type: " + userIDType); String path = "/" + targetGroupName + "/userMembers/" + NetUtil.encode(userID.getName()) + "?idType=" + userIDType; URL groupsURL = lookupServiceURL(Standards.GMS_GROUPS_01); URL removeUserMemberURL = new URL(groupsURL.toExternalForm() + path); log.debug("removeUserMember: " + removeUserMemberURL.toString()); // reset the state of the cache clearCache(); HttpDelete delete = new HttpDelete(removeUserMemberURL, true); delete.setSSLSocketFactory(getSSLSocketFactory()); delete.run(); Throwable error = delete.getThrowable(); if (error != null) { // transfer returns a -1 code for anonymous access. if (error instanceof AccessControlException) { throw new AccessControlException(error.getMessage()); } if (delete.getResponseCode() == 400) { throw new IllegalArgumentException(error.getMessage()); } if (error instanceof FileNotFoundException) { String errMessage = error.getMessage(); if (errMessage != null && errMessage.toLowerCase().contains("user")) throw new UserNotFoundException(errMessage); else throw new GroupNotFoundException(errMessage); } throw new IOException(error); } } private Principal getCurrentUserID() { Subject cur = AuthenticationUtil.getCurrentSubject(); if (cur == null) return null; // throw new IllegalArgumentException("no subject"); Set ps = cur.getPrincipals(HttpPrincipal.class); // hack if (ps.isEmpty()) return null; // throw new IllegalArgumentException("no principals"); Principal p = ps.iterator().next(); log.debug("getCurrentID: " + p.getClass()); return p; } /** * Get memberships for the current user (subject). * * @param role * @return A list of groups for which the current user has the role. * @throws AccessControlException * @throws ca.nrc.cadc.ac.UserNotFoundException * @throws java.io.IOException */ public List getMemberships(Role role) throws UserNotFoundException, AccessControlException, IOException { return getMemberships(null, role); } private List getMemberships(Principal ignore, Role role) throws UserNotFoundException, AccessControlException, IOException { if (role == null) { throw new IllegalArgumentException("role are required."); } Principal userID = getCurrentUserID(); if (userID != null) { List cachedGroups = getCachedGroups(userID, role, true); if (cachedGroups != null) { return cachedGroups; } } String roleString = role.getValue(); URL searchURL = lookupServiceURL(Standards.GMS_SEARCH_01); URL getMembershipsURL = new URL(searchURL.toExternalForm() + "?ROLE=" + NetUtil.encode(roleString)); log.debug("getMemberships request to " + getMembershipsURL.toString()); ByteArrayOutputStream out = new ByteArrayOutputStream(); HttpDownload transfer = new HttpDownload(getMembershipsURL, out); transfer.setSSLSocketFactory(getSSLSocketFactory()); transfer.run(); Throwable error = transfer.getThrowable(); if (error != null) { log.debug("getMemberships throwable", error); // transfer returns a -1 code for anonymous access. if ((transfer.getResponseCode() == -1) || (transfer.getResponseCode() == 401) || (transfer.getResponseCode() == 403)) { throw new AccessControlException(error.getMessage()); } if (transfer.getResponseCode() == 404) { throw new UserNotFoundException(error.getMessage()); } if (transfer.getResponseCode() == 400) { throw new IllegalArgumentException(error.getMessage()); } throw new IOException(error); } try { String groupsXML = new String(out.toByteArray(), "UTF-8"); log.debug("getMemberships returned: " + groupsXML); GroupListReader groupListReader = new GroupListReader(); List groups = groupListReader.read(groupsXML); setCachedGroups(userID, groups, role); return groups; } catch (Exception bug) { log.error("Unexpected exception", bug); throw new RuntimeException(bug); } } /** * Return the group, specified by parameter groupName, if the user, * identified by userID, is a member of that group. Return null * otherwise. * * This call is identical to getMemberShip(userID, groupName, Role.MEMBER) * * @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 IllegalArgumentException If a parameter is null. * @throws IOException If an unknown error occured. */ public Group getMembership(String groupName) throws UserNotFoundException, AccessControlException, IOException { return getMembership(groupName, Role.MEMBER); } /** * Return the group, specified by paramter groupName, if the user, * identified by userID, is a member (of type role) of that group. * Return null otherwise. * * @param groupName Identifies the group. * @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 IllegalArgumentException If a parameter is null. * @throws IOException If an unknown error occured. */ public Group getMembership(String groupName, Role role) throws UserNotFoundException, AccessControlException, IOException { if (groupName == null || role == null) { throw new IllegalArgumentException("groupName and role are required."); } Principal userID = getCurrentUserID(); if (userID != null) { Group cachedGroup = getCachedGroup(userID, groupName, role); if (cachedGroup != null) { return cachedGroup; } } String roleString = role.getValue(); String searchGroupPath = "?ROLE=" + NetUtil.encode(roleString) + "&GROUPID=" + NetUtil.encode(groupName); URL searchURL = lookupServiceURL(Standards.GMS_SEARCH_01); URL getMembershipURL = new URL(searchURL.toExternalForm() + searchGroupPath); log.debug("getMembership request to " + getMembershipURL.toString()); ByteArrayOutputStream out = new ByteArrayOutputStream(); HttpDownload transfer = new HttpDownload(getMembershipURL, out); transfer.setSSLSocketFactory(getSSLSocketFactory()); transfer.run(); Throwable error = transfer.getThrowable(); if (error != null) { log.debug("getMembership throwable", error); // transfer returns a -1 code for anonymous access. if ((transfer.getResponseCode() == -1) || (transfer.getResponseCode() == 401) || (transfer.getResponseCode() == 403)) { throw new AccessControlException(error.getMessage()); } if (transfer.getResponseCode() == 404) { throw new UserNotFoundException(error.getMessage()); } if (transfer.getResponseCode() == 400) { throw new IllegalArgumentException(error.getMessage()); } throw new IOException(error); } try { String groupsXML = new String(out.toByteArray(), "UTF-8"); log.debug("getMembership returned: " + groupsXML); GroupListReader groupListReader = new GroupListReader(); List groups = groupListReader.read(groupsXML); if (groups.isEmpty()) { return null; } if (groups.size() == 1) { Group ret = groups.get(0); addCachedGroup(userID, ret, role); return ret; } throw new IllegalStateException( "Duplicate membership for " + userID + " in group " + groupName); } catch (Exception bug) { log.error("Unexpected exception", bug); throw new RuntimeException(bug); } } /** * Check group membership of the current Subject. * * @param groupName * @return true if the current Subject is a member of the group, false otherwise * @throws UserNotFoundException * @throws AccessControlException * @throws IOException */ public boolean isMember(String groupName) throws UserNotFoundException, AccessControlException, IOException { return isMember(groupName, Role.MEMBER); } /** * * @param groupName * @param role * @return true if the current Subject is a member of the group with the specified role, false otherwise * @throws UserNotFoundException * @throws AccessControlException * @throws IOException */ public boolean isMember(String groupName, Role role) throws UserNotFoundException, AccessControlException, IOException { return isMember(getCurrentUserID(), groupName, role); } private boolean isMember(Principal userID, String groupName, Role role) throws UserNotFoundException, AccessControlException, IOException { Group group = getMembership(groupName, role); return group != null; } /** * @param sslSocketFactory the sslSocketFactory to set */ public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { if (mySocketFactory != null) throw new IllegalStateException("Illegal use of GMSClient: " + "cannot set SSLSocketFactory after using one created from Subject"); this.sslSocketFactory = sslSocketFactory; clearCache(); } private int subjectHashCode = 0; private SSLSocketFactory getSSLSocketFactory() { AccessControlContext ac = AccessController.getContext(); Subject s = Subject.getSubject(ac); // no real Subject: can only use the one from setSSLSocketFactory if (s == null || s.getPrincipals().isEmpty()) { return sslSocketFactory; } // lazy init if (this.mySocketFactory == null) { log.debug("getSSLSocketFactory: " + s); this.mySocketFactory = SSLUtil.getSocketFactory(s); this.subjectHashCode = s.hashCode(); } else { int c = s.hashCode(); if (c != subjectHashCode) throw new IllegalStateException("Illegal use of " + this.getClass().getSimpleName() + ": subject change not supported for internal SSLSocketFactory"); } return this.mySocketFactory; } protected void clearCache() { AccessControlContext acContext = AccessController.getContext(); Subject subject = Subject.getSubject(acContext); if (subject != null) { subject.getPrivateCredentials().remove(new GroupMemberships()); } } protected GroupMemberships getGroupCache(Principal userID) { AccessControlContext acContext = AccessController.getContext(); Subject subject = Subject.getSubject(acContext); // only consult cache if the userID is of the calling subject if (userIsSubject(userID, subject)) { Set gset = subject.getPrivateCredentials(GroupMemberships.class); if (gset == null || gset.isEmpty()) { GroupMemberships mems = new GroupMemberships(serviceID.toString(), userID); subject.getPrivateCredentials().add(mems); return mems; } GroupMemberships mems = gset.iterator().next(); // check to ensure they have the same service URI if (!serviceID.toString().equals(mems.getServiceURI())) { log.debug("Not using cache because of differing service URIs: " + "[" + serviceID.toString() + "][" + mems.getServiceURI() + "]"); return null; } return mems; } return null; // no cache } protected Group getCachedGroup(Principal userID, String groupID, Role role) { List groups = getCachedGroups(userID, role, false); if (groups == null) return null; // no cache for (Group g : groups) { if (g.getID().getName().equals(groupID)) return g; } return null; } protected List getCachedGroups(Principal userID, Role role, boolean complete) { GroupMemberships mems = getGroupCache(userID); if (mems == null) return null; // no cache Boolean cacheState = mems.isComplete(role); if (!complete || Boolean.TRUE.equals(cacheState)) return mems.getMemberships(role); // caller wanted complete and we don't have that return null; } protected void addCachedGroup(Principal userID, Group group, Role role) { GroupMemberships mems = getGroupCache(userID); if (mems == null) return; // no cache mems.add(group, role); } protected void setCachedGroups(Principal userID, List groups, Role role) { GroupMemberships mems = getGroupCache(userID); if (mems == null) return; // no cache mems.add(groups, role); } protected boolean userIsSubject(Principal userID, Subject subject) { if (userID == null || subject == null) { return false; } for (Principal subjectPrincipal : subject.getPrincipals()) { if (AuthenticationUtil.equals(subjectPrincipal, userID)) { return true; } } return false; } protected RegistryClient getRegistryClient() { return new RegistryClient(); } /** * Lookup the Service URL for the given standard. The current AuthMethod * will be taken into account. * * @param standard The URI standard to look up. * @return URL for the service. * @throws AccessControlException If the URL cannot be found for the * provided AuthMethod. */ private URL lookupServiceURL(final URI standard) throws AccessControlException { Subject subject = AuthenticationUtil.getCurrentSubject(); AuthMethod am = getAuthMethod(subject); URL serviceURL = getRegistryClient().getServiceURL(this.serviceID, standard, am); // now that we have a URL we can check if the cookie will actually be sent to it if (AuthMethod.COOKIE.equals(am)) { try { boolean domainMatch = false; String domain = NetUtil.getDomainName(serviceURL); for (SSOCookieCredential cc : subject.getPublicCredentials(SSOCookieCredential.class)) { if (cc.getDomain().equals(domain)) domainMatch = true; } if (!domainMatch) { throw new AccessControlException("no SSOCookieCredential for domain " + domain); } } catch(IOException ex) { throw new RuntimeException("failure checking domain for cookie use", ex); } } if (serviceURL == null) { throw new RuntimeException( String.format("Unable to get Service URL for '%s', '%s', '%s'", serviceID.toString(), standard, am)); } return serviceURL; } private AuthMethod getAuthMethod(Subject subject) { if (subject != null) { // web services use CDP to load a proxy cert so prefer that X509CertificateChain privateKeyChain = X509CertificateChain.findPrivateKeyChain( subject.getPublicCredentials()); if (privateKeyChain != null) return AuthMethod.CERT; // ui applications pass cookie(s) along Set sso = subject.getPublicCredentials(SSOCookieCredential.class); if ( !sso.isEmpty() ) { return AuthMethod.COOKIE; } // AuthMethod.PASSWORD not supported // AuthMethod.TOKEN not supported throw new AccessControlException("No valid public credentials."); } else { throw new AccessControlException("Anonymous access not supported."); } } }