diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java index 772c6531a7cdfc147731f570795620cbde4dd3c6..1d4af6cbc290703f8058d09a5f54c1562c17eaf1 100755 --- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java @@ -77,7 +77,9 @@ import ca.nrc.cadc.ac.UserNotFoundException; import ca.nrc.cadc.ac.UserRequest; import ca.nrc.cadc.auth.AuthenticationUtil; import ca.nrc.cadc.auth.HttpPrincipal; +import ca.nrc.cadc.auth.NumericPrincipal; import ca.nrc.cadc.net.TransientException; + import com.unboundid.ldap.sdk.AddRequest; import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.BindRequest; @@ -100,9 +102,11 @@ import com.unboundid.ldap.sdk.SimpleBindRequest; import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedRequest; import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedResult; + import org.apache.log4j.Logger; import javax.security.auth.x500.X500Principal; + import java.security.AccessControlException; import java.security.Principal; import java.util.ArrayList; @@ -459,7 +463,6 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO SearchRequest searchRequest = new SearchRequest(usersDN, SearchScope.SUB, searchField, userAttribs); - if (isSecure(usersDN)) { searchRequest.addControl( @@ -480,10 +483,13 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO logger.debug(msg); throw new UserNotFoundException(msg); } + User<T> user = new User<T>(userID); user.getIdentities().add(new HttpPrincipal(searchResult.getAttributeValue( userLdapAttrib.get(HttpPrincipal.class)))); - + String dn = searchResult.getAttributeValue(LDAP_DISTINGUISHED_NAME); + user.getIdentities().add(new X500Principal(dn)); + String fname = searchResult.getAttributeValue(LDAP_FIRST_NAME); String lname = searchResult.getAttributeValue(LDAP_LAST_NAME); PersonalDetails personaDetails = new PersonalDetails(fname, lname); diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/GetUserAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/GetUserAction.java index 4793c719fb078488473eaca1bbb215c17a9a2aac..dfef465d1e458e08465df2ddf9952b15dfe15d34 100644 --- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/GetUserAction.java +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/GetUserAction.java @@ -137,7 +137,6 @@ public class GetUserAction extends AbstractUserAction protected boolean isServops() { - log.debug("alinga-- isServops(): augmentUserDN = " + this.augmentUserDN); boolean isServops = false; AccessControlContext acc = AccessController.getContext(); Subject subject = Subject.getSubject(acc); diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserActionFactory.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserActionFactory.java index ae8ec41014d2f0ed8d67a7bc50a664c270de1542..6982a37b96dfbd72cb1cb513605aba727dc15221 100644 --- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserActionFactory.java +++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserActionFactory.java @@ -234,24 +234,24 @@ public abstract class UserActionFactory { throw new IllegalArgumentException("User endpoint missing idType parameter"); } - else if (idType.equals(IdentityType.USERNAME.getValue())) + else if (idType.equalsIgnoreCase(IdentityType.USERNAME.getValue())) { return new User<HttpPrincipal>(new HttpPrincipal(userName)); } - else if (idType.equals(IdentityType.X500.getValue())) + else if (idType.equalsIgnoreCase(IdentityType.X500.getValue())) { return new User<X500Principal>(new X500Principal(userName)); } - else if (idType.equals(IdentityType.UID.getValue())) + else if (idType.equalsIgnoreCase(IdentityType.UID.getValue())) { return new User<NumericPrincipal>(new NumericPrincipal( Long.parseLong(userName))); } - else if (idType.equals(IdentityType.OPENID.getValue())) + else if (idType.equalsIgnoreCase(IdentityType.OPENID.getValue())) { return new User<OpenIdPrincipal>(new OpenIdPrincipal(userName)); } - else if (idType.equals(IdentityType.COOKIE.getValue())) + else if (idType.equalsIgnoreCase(IdentityType.COOKIE.getValue())) { return new User<CookiePrincipal>(new CookiePrincipal(userName)); } diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/UserClient.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/UserClient.java new file mode 100644 index 0000000000000000000000000000000000000000..7a6d8a416c704f3c08446722e5e60a3593a6d7d2 --- /dev/null +++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/UserClient.java @@ -0,0 +1,354 @@ +/* + ************************************************************************ + ******************* 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 java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.Principal; +import java.util.Set; + +import javax.net.ssl.SSLSocketFactory; +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; + +import ca.nrc.cadc.ac.*; +import ca.nrc.cadc.auth.HttpPrincipal; + +import org.apache.log4j.Logger; + +import ca.nrc.cadc.ac.xml.UserReader; +import ca.nrc.cadc.auth.CookiePrincipal; +import ca.nrc.cadc.auth.NumericPrincipal; +import ca.nrc.cadc.auth.SSLUtil; +import ca.nrc.cadc.net.HttpDownload; + + +/** + * Client class for performing user searching and user actions + * with the access control web service. + */ +public class UserClient +{ + private static final Logger log = Logger.getLogger(UserClient.class); + + // socket factory to use when connecting + private SSLSocketFactory sslSocketFactory; + private SSLSocketFactory mySocketFactory; + private String baseURL; + + /** + * Constructor. + * + * @param baseURL The URL of the supporting access control web service + * obtained from the registry. + */ + public UserClient(final String baseURL) + throws IllegalArgumentException + { + if (baseURL == null) + { + throw new IllegalArgumentException("baseURL is required"); + } + try + { + new URL(baseURL); + } + catch (MalformedURLException e) + { + throw new IllegalArgumentException("URL is malformed: " + + e.getMessage()); + } + + if (baseURL.endsWith("/")) + { + this.baseURL = baseURL.substring(0, baseURL.length() - 1); + } + else + { + this.baseURL = baseURL; + } + } + + /** + * This method takes a subject with at least one valid principal, + * uses the ac user web service to get all the other + * associated principals which are then added to the subject. + * + * @param subject The Subject to pull Princials for. + */ + public void augmentSubject(Subject subject) + { + URL url = this.getURL(subject); + log.debug("augmentSubject request to " + url.toString()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + HttpDownload download = new HttpDownload(url, out); + + download.setSSLSocketFactory(getSSLSocketFactory()); + download.run(); + + this.handleThrowable(download); + this.augmentSubject(subject, this.getPrincipals(out)); + } + + protected void augmentSubject(Subject subject, Set<Principal> principals) + { + if (principals.isEmpty()) + { + String name = subject.getPrincipals().iterator().next().getName(); + String msg = "No UserIdentity in LDAP server for principal: " + name; + throw new IllegalStateException(msg); + } + + for (Principal principal : principals) + { + if (principal instanceof HttpPrincipal) + { + subject.getPrincipals(HttpPrincipal.class) + .add((HttpPrincipal)principal); + } + else if (principal instanceof X500Principal) + { + subject.getPrincipals(X500Principal.class) + .add((X500Principal)principal); + } + else if (principal instanceof NumericPrincipal) + { + subject.getPrincipals(NumericPrincipal.class) + .add((NumericPrincipal)principal); + } + else if (principal instanceof CookiePrincipal) + { + subject.getPrincipals(CookiePrincipal.class) + .add((CookiePrincipal)principal); + } + else + { + final String msg = "Subject has unsupported principal " + + principal.getName() + + ", not one of (X500, Cookie, HTTP or Cadc)."; + throw new IllegalStateException(msg); + } + } + } + + protected Set<Principal> getPrincipals(ByteArrayOutputStream out) + { + try + { + String userXML = new String(out.toByteArray(), "UTF-8"); + log.debug("userXML Input to getPrinciplas(): " + userXML); + + User<Principal> user = new UserReader().read(userXML); + return user.getIdentities(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + protected void handleThrowable(HttpDownload download) + { + Throwable throwable = download.getThrowable(); + if (throwable != null) + { + log.debug("handleThrowable(): throwable (" + download + .getResponseCode() + ")", throwable); + throw new IllegalStateException(throwable.getMessage()); + } + } + + protected URL getURL(Subject subject) + { + try + { + String userID = subject.getPrincipals().iterator().next().getName(); + String encodedUserID = URLEncoder.encode(userID, "UTF-8"); + URL url = new URL(this.baseURL + "/users/" + encodedUserID + + "?idType=" + this.getIdType(subject) + "&detail=identity"); + log.debug("getURL(): returned url =" + + "" + + " " + url.toString()); + return url; + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + } + + protected String getIdType(Subject subject) + { + Set<Principal> principals = subject.getPrincipals(); + if (principals.size() > 0) + { + String idTypeStr = null; + Principal principal = principals.iterator().next(); + if (principal instanceof HttpPrincipal) + { + idTypeStr = IdentityType.USERNAME.getValue(); + } + else if (principal instanceof X500Principal) + { + idTypeStr = IdentityType.X500.getValue(); + } + else if (principal instanceof NumericPrincipal) + { + idTypeStr = IdentityType.UID.getValue(); + } + else if (principal instanceof CookiePrincipal) + { + idTypeStr = IdentityType.COOKIE.getValue(); + } + else + { + final String msg = "Subject has unsupported principal " + + principal.getName() + + ", not one of (X500, Cookie, HTTP or Cadc)."; + throw new IllegalArgumentException(msg); + } + + return idTypeStr; + } + else + { + final String msg = "Subject has no principal."; + throw new IllegalArgumentException(msg); + } + } + + /** + * @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) + { + log.debug("Clearing cache"); + subject.getPrivateCredentials().clear(); + } + } +}