package org.dcache.auth.gplazma;
import org.globus.gsi.gssapi.jaas.GlobusPrincipal;
import javax.security.auth.kerberos.KerberosPrincipal;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.security.Principal;
import java.util.Properties;
import java.util.Set;
import org.dcache.auth.GidPrincipal;
import org.dcache.auth.KAuthFile;
import org.dcache.auth.LoginNamePrincipal;
import org.dcache.auth.PasswordCredential;
import org.dcache.auth.UidPrincipal;
import org.dcache.auth.UserAuthBase;
import org.dcache.auth.UserAuthRecord;
import org.dcache.auth.UserNamePrincipal;
import org.dcache.auth.UserPwdRecord;
import org.dcache.auth.attributes.HomeDirectory;
import org.dcache.auth.attributes.Restrictions;
import org.dcache.auth.attributes.RootDirectory;
import org.dcache.gplazma.AuthenticationException;
import org.dcache.gplazma.plugins.GPlazmaAccountPlugin;
import org.dcache.gplazma.plugins.GPlazmaAuthenticationPlugin;
import org.dcache.gplazma.plugins.GPlazmaMappingPlugin;
import org.dcache.gplazma.plugins.GPlazmaSessionPlugin;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getFirst;
import static org.dcache.gplazma.util.Preconditions.checkAuthentication;
/**
* A principal that represents an entry in a kpwd file.
*
* Used internally by the KpwdPlugin to pass along identifying
* information between the auth, map, account, and session steps.
*/
@SuppressWarnings("deprecation")
class KpwdPrincipal
implements Principal, Serializable
{
private static final long serialVersionUID = -5104794169722666904L;
final String name;
final long uid;
final long[] gids;
final boolean isDisabled;
final String home;
final String root;
final boolean isReadOnly;
public KpwdPrincipal(UserAuthBase record) {
checkNotNull(record);
name = record.Username;
uid = record.UID;
home = record.Home;
root = record.Root;
isReadOnly = record.ReadOnly;
gids = new long[record.GIDs.size()];
for (int i = 0; i < gids.length; i++ ){
gids[i] = record.GIDs.get(i);
}
if (record instanceof UserPwdRecord) {
isDisabled = ((UserPwdRecord)record).isDisabled();
} else {
isDisabled = false;
}
}
@Override
public String getName()
{
return name;
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + getName() + "]";
}
}
public class KpwdPlugin
implements GPlazmaAuthenticationPlugin,
GPlazmaMappingPlugin,
GPlazmaAccountPlugin,
GPlazmaSessionPlugin
{
private static final String KPWD = "gplazma.kpwd.file";
private final File _kpwdFile;
private long _cacheTime;
private KAuthFile _cacheAuthFile;
public KpwdPlugin(Properties properties)
{
String path = properties.getProperty(KPWD, null);
checkArgument(path != null, KPWD + " argument must be specified");
_kpwdFile = new File(path);
}
/** Constructor for testing. */
KpwdPlugin(KAuthFile file)
{
_cacheAuthFile = file;
_kpwdFile = null;
}
private synchronized KAuthFile getAuthFile()
throws AuthenticationException
{
try {
if (_kpwdFile != null && _kpwdFile.lastModified() >= _cacheTime) {
_cacheAuthFile = new KAuthFile(_kpwdFile.getPath());
_cacheTime = System.currentTimeMillis();
}
return _cacheAuthFile;
} catch (IOException e) {
String msg = String.format("failed to read %s: %s",
_kpwdFile.getName(), e.getMessage());
throw new AuthenticationException(msg, e);
}
}
/**
* Password authentication.
*
* Authenticates login name + password and generates a
* KpwdPrincipal.
*/
@SuppressWarnings("null")
@Override
public void authenticate(Set<Object> publicCredentials,
Set<Object> privateCredentials,
Set<Principal> identifiedPrincipals)
throws AuthenticationException
{
PasswordCredential password =
getFirst(filter(privateCredentials, PasswordCredential.class), null);
checkAuthentication(password != null, "no username and password");
String name = password.getUsername();
UserPwdRecord entry = getAuthFile().getUserPwdRecord(name);
checkAuthentication(entry != null, name + " is unknown");
checkAuthentication(entry.isAnonymous() || entry.isDisabled() ||
entry.passwordIsValid(String.valueOf(password.getPassword())),
"wrong password");
/* NOTE: We add the principal even when the account is
* disabled (banned) and we do so without checking the password; this
* is to allow banning during the account step.
*/
identifiedPrincipals.add(new KpwdPrincipal(entry));
checkAuthentication(!entry.isDisabled(), "account is disabled");
}
/**
* Maps KpwdPrincipal, DN, and Kerberos Principal to UserName, UID
* and GID.
*
* Authorizes user name, DN, Kerberos principal, UID and GID.
*/
@SuppressWarnings({ "null", "deprecation" })
@Override
public void map(Set<Principal> principals)
throws AuthenticationException
{
KpwdPrincipal kpwd =
getFirst(filter(principals, KpwdPrincipal.class), null);
if (kpwd == null) {
KAuthFile authFile = getAuthFile();
String loginName = null;
Principal principal = null;
for (Principal p: principals) {
if (p instanceof LoginNamePrincipal) {
checkAuthentication(loginName == null,
errorMessage(principal, p));
loginName = p.getName();
} else if (p instanceof GlobusPrincipal) {
checkAuthentication(principal == null,
errorMessage(principal, p));
principal = p;
} else if (p instanceof KerberosPrincipal) {
checkAuthentication(principal == null,
errorMessage(principal, p));
principal = p;
} else if (p instanceof UserNamePrincipal) {
/*
* This case handles e.g. authenticated dcap
* doors, particularly statement like
*
* mapping "user1" user2
*
* in kpwd file
*/
checkAuthentication(principal == null,
errorMessage(principal, p));
principal = p;
}
}
checkAuthentication(principal != null, "no mappable principals");
if (loginName == null) {
loginName = authFile.getIdMapping(principal.getName());
checkAuthentication(loginName != null, "no login name");
}
UserAuthRecord authRecord = authFile.getUserRecord(loginName);
checkAuthentication(authRecord != null, "unknown login name: " +
loginName);
checkAuthentication(authRecord.hasSecureIdentity(principal.getName()),
"not allowed to login as " + loginName);
authRecord.DN = principal.getName();
kpwd = new KpwdPrincipal(authRecord);
}
principals.add(kpwd);
/* We explicitly check whether the user record is banned and
* don't authorize the remaining principals. We do however
* authorize the KpwdPrincipal to allow blacklisting in the
* account step.
*/
checkAuthentication(!kpwd.isDisabled, "account disabled");
principals.add(new UserNamePrincipal(kpwd.getName()));
principals.add(new UidPrincipal(kpwd.uid));
boolean primary = true;
for (long gid: kpwd.gids) {
principals.add(new GidPrincipal(gid, primary));
primary = false;
}
}
private static String errorMessage(Principal principal1,
Principal principal2)
{
if(principal1 == null || principal2 == null) {
return "";
}
String name1 = nameFor(principal1);
String name2 = nameFor(principal2);
if (name1.equals(name2)) {
return "multiple " + name2 + " principals found";
} else {
return name1 + " and " + name2 + " principals found";
}
}
@SuppressWarnings("deprecation")
private static String nameFor(Principal principal)
{
if(principal instanceof KerberosPrincipal) {
return "Kerberos";
} else if(principal instanceof GlobusPrincipal) {
return "X509";
} else {
return principal.getClass().getSimpleName();
}
}
/**
* Checks whether KpwdPrincipal is flagged as disabled.
*/
@Override
public void account(Set<Principal> authorizedPrincipals)
throws AuthenticationException
{
KpwdPrincipal kpwd =
getFirst(filter(authorizedPrincipals, KpwdPrincipal.class), null);
checkAuthentication(kpwd == null || !kpwd.isDisabled,
"account disabled");
}
/**
* Assigns home, root and read only attributes from KpwdPrincipal.
*/
@SuppressWarnings("null")
@Override
public void session(Set<Principal> authorizedPrincipals,
Set<Object> attrib)
throws AuthenticationException
{
KpwdPrincipal kpwd =
getFirst(filter(authorizedPrincipals, KpwdPrincipal.class), null);
checkAuthentication(kpwd != null, "no record found");
attrib.add(new HomeDirectory(kpwd.home));
attrib.add(new RootDirectory(kpwd.root));
if (kpwd.isReadOnly) {
attrib.add(Restrictions.readOnly());
}
}
}