package org.dcache.auth; import com.google.common.net.InetAddresses; import org.globus.gsi.gssapi.jaas.GlobusPrincipal; import javax.security.auth.Subject; import javax.security.auth.kerberos.KerberosPrincipal; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import static java.util.Arrays.asList; public class Subjects { public static final String UNKNOWN = "<unknown>"; /** * Ordered list of principals considered as displayable. */ private static final Class<? extends Principal>[] DISPLAYABLE = new Class[] { FullNamePrincipal.class, UserNamePrincipal.class, GlobusPrincipal.class, KerberosPrincipal.class, Origin.class, Principal.class }; /** * The subject representing the root user, that is, a user that is * empowered to do everything. */ public static final Subject ROOT; public static final Subject NOBODY; static { ROOT = new Subject(); ROOT.getPrincipals().add(new UidPrincipal(0)); ROOT.getPrincipals().add(new GidPrincipal(0, true)); ROOT.setReadOnly(); NOBODY = new Subject(); NOBODY.setReadOnly(); } /** * Returns true if and only if the subject is root, that is, has * the user ID 0. */ public static boolean isRoot(Subject subject) { return hasUid(subject, 0); } /** * Returns true if and only if the subject is nobody, i.e., does * not have a UID. * * Being nobody does not imply that the user is anonymous: The * subjects's identiy may have been established through some * authentication mechanism. However the subject could not be * assigned an internal identity in dCache. */ public static boolean isNobody(Subject subject) { for (Principal principal: subject.getPrincipals()) { if (principal instanceof UidPrincipal) { return false; } } return true; } /** * Returns true if and only if the subject has the given user ID. */ public static boolean hasUid(Subject subject, long uid) { Set<UidPrincipal> principals = subject.getPrincipals(UidPrincipal.class); for (UidPrincipal principal : principals) { if (principal.getUid() == uid) { return true; } } return false; } /** * Returns true if and only if the subject has the given group ID. */ public static boolean hasGid(Subject subject, long gid) { Set<GidPrincipal> principals = subject.getPrincipals(GidPrincipal.class); for (GidPrincipal principal : principals) { if (principal.getGid() == gid) { return true; } } return false; } /** * Returns the users IDs of a subject. */ public static long[] getUids(Subject subject) { Set<UidPrincipal> principals = subject.getPrincipals(UidPrincipal.class); long[] uids = new long[principals.size()]; int i = 0; for (UidPrincipal principal : principals) { uids[i++] = principal.getUid(); } return uids; } /** * Returns the principal of the given type of the subject. Returns * null if there is no such principal. * * @throws IllegalArgumentException is subject has more than one such principal */ private static <T> T getUniquePrincipal(Subject subject, Class<T> type) throws IllegalArgumentException { T result = null; if( subject == null) { return null; } for (Principal principal: subject.getPrincipals()) { if (type.isInstance(principal)) { if (result != null) { throw new IllegalArgumentException("Subject has multiple principals of type " + type.getSimpleName()); } result = type.cast(principal); } } return result; } /** * Returns the UID of a subject. * * @throws NoSuchElementException if subject has no UID * @throws IllegalArgumentException is subject has more than one UID */ public static long getUid(Subject subject) throws NoSuchElementException, IllegalArgumentException { UidPrincipal uid = getUniquePrincipal(subject, UidPrincipal.class); if (uid == null) { throw new NoSuchElementException("Subject has no UID"); } return uid.getUid(); } /** * Returns the group IDs of a subject. If the user has a primary * group, then first element will be a primary group ID. */ public static long[] getGids(Subject subject) { Set<GidPrincipal> principals = subject.getPrincipals(GidPrincipal.class); long[] gids = new long[principals.size()]; int i = 0; for (GidPrincipal principal : principals) { if (principal.isPrimaryGroup()) { gids[i++] = gids[0]; gids[0] = principal.getGid(); } else { gids[i++] = principal.getGid(); } } return gids; } /** * Returns the primary group ID of a subject. * * @throws NoSuchElementException if subject has no primary GID * @throws IllegalArgumentException if subject has several primary GID */ public static long getPrimaryGid(Subject subject) throws NoSuchElementException, IllegalArgumentException { Set<GidPrincipal> principals = subject.getPrincipals(GidPrincipal.class); int counter = 0; long gid = 0; for (GidPrincipal principal : principals) { if (principal.isPrimaryGroup()) { gid = principal.getGid(); counter++; } } if (counter == 0) { throw new NoSuchElementException("Subject has no primary GID"); } if (counter > 1) { throw new IllegalArgumentException("Subject has multiple primary GIDs"); } return gid; } /** * Returns the origin of a subject. Returns null if subject has no * origin. * * @throws IllegalArgumentException if there is more than one origin */ public static Origin getOrigin(Subject subject) throws IllegalArgumentException { return getUniquePrincipal(subject, Origin.class); } /** * Returns the DN of a subject. Returns null if subject has no DN. * * @throws IllegalArgumentException if there is more than one origin */ public static String getDn(Subject subject) throws IllegalArgumentException { GlobusPrincipal principal = getUniquePrincipal(subject, GlobusPrincipal.class); return (principal == null) ? null : principal.getName(); } /** * Returns the primary FQANs of a subject. Returns null if subject * has no primary FQAN. * * @throws IllegalArgumentException if subject has more than one * primary FQANs */ public static FQAN getPrimaryFqan(Subject subject) throws IllegalArgumentException { Set<FQANPrincipal> principals = subject.getPrincipals(FQANPrincipal.class); FQAN fqan = null; for (FQANPrincipal principal: principals) { if (principal.isPrimaryGroup()) { if (fqan != null) { throw new IllegalArgumentException("Subject has multiple primary FQANs"); } fqan = principal.getFqan(); } } return fqan; } /** * Returns the collection of FQANs of a subject. */ public static Collection<FQAN> getFqans(Subject subject) { Collection<FQAN> fqans = new ArrayList<>(); for (Principal principal: subject.getPrincipals()) { if (principal instanceof FQANPrincipal) { fqans.add(((FQANPrincipal) principal).getFqan()); } } return fqans; } /** * Returns the the user name of a subject. If UserNamePrincipal is * not defined then null is returned. * * @throws IllegalArgumentException if subject has more than one * user name */ public static String getUserName(Subject subject) { UserNamePrincipal principal = getUniquePrincipal(subject, UserNamePrincipal.class); return (principal == null) ? null : principal.getName(); } /** * Returns the the login name of a subject. If LoginNamePrincipal * is not defined then null is returned. * * @throws IllegalArgumentException if subject has more than one * login name */ public static String getLoginName(Subject subject) { LoginNamePrincipal principal = getUniquePrincipal(subject, LoginNamePrincipal.class); return (principal == null) ? null : principal.getName(); } /** * Returns a displayable name derived from one of the principals * of the Subject. */ public static String getDisplayName(Subject subject) { for (Class<? extends Principal> clazz: DISPLAYABLE) { Set<? extends Principal> principals = subject.getPrincipals(clazz); if (!principals.isEmpty()) { return principals.iterator().next().getName(); } } return UNKNOWN; } /** * Returns the "Kerberos principal" for the user (as specified in * Section 2.1 of RFC 1964) if they logged in via Kerberos or null if * Kerberos was not used. * @throws IllegalArgumentException if the subject contains multiple * KerberosPrincipal. */ public static String getKerberosName(Subject subject) throws IllegalArgumentException { KerberosPrincipal principal = getUniquePrincipal(subject, KerberosPrincipal.class); return (principal == null) ? null : principal.getName(); } /** * Maps a UserAuthBase to a Subject. The Subject will contain the * UID (UidPrincipal), GID (GidPrincipal), user name * (UserNamePrincipal), DN (GlobusPrincipal), and FQAN * (FQANPrincipal) principals. * * @param user UserAuthBase to convert * @param primary Whether the groups of user are the primary groups */ public static final Subject getSubject(UserAuthBase user, boolean primary) { Subject subject = new Subject(); Set<Principal> principals = subject.getPrincipals(); principals.add(new UidPrincipal(user.UID)); boolean isPrimary = primary; for (int gid: user.GIDs) { principals.add(new GidPrincipal(gid, isPrimary)); isPrimary = false; } String name = user.Username; if (name != null && !name.isEmpty()) { principals.add(new UserNamePrincipal(name)); } String dn = user.DN; if (dn != null && !dn.isEmpty()) { principals.add(new GlobusPrincipal(dn)); } String fqan = user.getFqan().toString(); if (fqan != null && !fqan.isEmpty()) { principals.add(new FQANPrincipal(fqan, primary)); } return subject; } /** * Maps a UserAuthRecord to a Subject. The Subject will contain * the UID (UidPrincipal), GID (GidPrincipal), user name * (UserNamePrincipal), DN (GlobusPrincipal), and FQAN * (FQANPrincipal) principals. * * @param user UserAuthRecord to convert */ public static final Subject getSubject(UserAuthRecord user) { Subject subject = new Subject(); Set<Principal> principals = subject.getPrincipals(); principals.add(new UidPrincipal(user.UID)); boolean primary = true; for (int gid: user.GIDs) { principals.add(new GidPrincipal(gid, primary)); primary = false; } String name = user.Username; if (name != null && !name.isEmpty()) { principals.add(new UserNamePrincipal(name)); } String dn = user.DN; if (dn != null && !dn.isEmpty()) { principals.add(new GlobusPrincipal(dn)); } FQAN fqan = user.getFqan(); if (fqan!=null) { String fqanstr = fqan.toString(); if (fqanstr != null && !fqanstr.isEmpty()) { principals.add(new FQANPrincipal(fqanstr, true)); } } return subject; } /** * Create a subject from a list of principals. The principals are * presented as String-based representations that are parsed. They * have a common format {@literal <type>:<value>} where * {@literal <type>} is one of name, kerberos, dn and dqan and * {@literal <value>} is a string representation of the principal. */ public static Subject subjectFromArgs(List<String> args) { Set<Principal> principals = principalsFromArgs(args); Set<Object> publicCredentials = Collections.emptySet(); Set<Object> privateCredentials = Collections.emptySet(); return new Subject(false, principals, publicCredentials, privateCredentials); } public static Set<Principal> principalsFromArgs(List<String> args) { Set<Principal> principals = new HashSet<>(); boolean isPrimaryFqan = true; for(String arg : args) { int idx = arg.indexOf(':'); if(idx == -1) { throw new IllegalArgumentException("format for principals is <type>:<value>"); } String type = arg.substring(0, idx); String value = arg.substring(idx+1); Principal principal; switch (type) { case "dn": principal = new GlobusPrincipal(value); break; case "kerberos": principal = new KerberosPrincipal(value); break; case "fqan": principal = new FQANPrincipal(value, isPrimaryFqan); isPrimaryFqan = false; break; case "name": principal = new LoginNamePrincipal(value); break; case "origin": principal = new Origin(InetAddresses.forString(value)); break; case "oidc": principal = new OidcSubjectPrincipal(value); break; case "email": principal = new EmailAddressPrincipal(value); break; case "user": principal = new UserNamePrincipal(value); break; default: try { Class<? extends Principal> principalClass = Class.forName(type).asSubclass(Principal.class); Constructor<? extends Principal> principalConstructor = principalClass.getConstructor(String.class); principal = principalConstructor.newInstance(value); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("No matching constructor found: "+type+"(String)"); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("No matching class found: "+type); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Invocation failed: "+e.toString()); } catch (InstantiationException e) { throw new IllegalArgumentException("Instantiation failed: "+e.toString()); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Access Exception: "+e.toString()); } } principals.add(principal); } return principals; } // Returned Subject must NOT be readOnly. public static Subject of(int uid, int gid, int[] gids) { Builder builder = of().uid(uid).gid(gid); for (int g : gids) { builder.gid(g); } return builder.build(); } public static Builder of() { return new Builder(); } public static class Builder { private final Subject _subject = new Subject(); private boolean haveFqan; private boolean haveGid; private boolean readOnly; public Subject build() { if (readOnly) { _subject.setReadOnly(); } return _subject; } private void add(Principal principal) { _subject.getPrincipals().add(principal); } public Builder readOnly() { readOnly = true; return this; } public Builder dn(String dn) { add(new GlobusPrincipal(dn)); return this; } public Builder uid(long uid) { add(new UidPrincipal(uid)); return this; } /** * Add a gid Principal. The first gid is automatically the primary * gid; any subsequent calls add non-primary gid principals. */ public Builder gid(long gid) { add(new GidPrincipal(gid, !haveGid)); haveGid = true; return this; } /** * Add an FQAN Principal. The first FQAN is automatically a * primary FQAN and subsequent FQAN are non-primary FQANs. */ public Builder fqan(String fqan) { return fqan(new FQAN(fqan)); } /** * Add an FQAN Principal. The first FQAN is automatically a * primary FQAN and subsequent FQAN are non-primary FQANs. */ public Builder fqan(FQAN fqan) { add(new FQANPrincipal(fqan, !haveFqan)); haveFqan = true; return this; } } }