/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.security.internal.realms;
import org.seedstack.seed.security.AuthenticationException;
import org.seedstack.seed.security.AuthenticationInfo;
import org.seedstack.seed.security.AuthenticationToken;
import org.seedstack.seed.security.IncorrectCredentialsException;
import org.seedstack.seed.security.Realm;
import org.seedstack.seed.security.RoleMapping;
import org.seedstack.seed.security.RolePermissionResolver;
import org.seedstack.seed.security.UnsupportedTokenException;
import org.seedstack.seed.security.X509CertificateToken;
import org.seedstack.seed.security.principals.PrincipalProvider;
import org.seedstack.seed.security.principals.Principals;
import org.seedstack.seed.security.principals.X509CertificatePrincipalProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.security.auth.x500.X500Principal;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* A realm that is based on an X509Certificate to identify the user and provide his roles. This realm does not actually authentifies the user as this
* process should be done by the JEE container.
*/
public class X509CertificateRealm implements Realm {
private static final Logger LOGGER = LoggerFactory.getLogger(X509CertificateRealm.class);
private RoleMapping roleMapping;
private RolePermissionResolver rolePermissionResolver;
private static final String UID = "UID";
private static final String CN = "CN";
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if (!(token instanceof X509CertificateToken)) {
throw new UnsupportedTokenException();
}
if (token.getPrincipal() == null) {
throw new IncorrectCredentialsException();
}
String uid = null;
String cn = null;
final X509Certificate[] certificates = ((X509CertificateToken) token).getAuthenticatingCertificates();
// we take the first certificate to extract username
String dn = certificates[0].getSubjectX500Principal().getName(X500Principal.RFC2253);
LdapName ln;
try {
ln = new LdapName(dn);
} catch (InvalidNameException e) {
throw new IncorrectCredentialsException("Certificate does not have a valid DN for user", e);
}
for (Rdn rdn : ln.getRdns()) {
if (rdn.getType().equalsIgnoreCase(UID)) {
uid = rdn.getValue().toString();
} else if (rdn.getType().equalsIgnoreCase(CN)) {
cn = rdn.getValue().toString();
}
}
X509CertificatePrincipalProvider x509Pp = new X509CertificatePrincipalProvider(certificates);
AuthenticationInfo authInfo;
if (uid == null) {
authInfo = new AuthenticationInfo(x509Pp, certificates);
} else {
authInfo = new AuthenticationInfo(uid, certificates);
authInfo.getOtherPrincipals().add(x509Pp);
}
if (cn != null) {
authInfo.getOtherPrincipals().add(Principals.fullNamePrincipal(cn));
}
return authInfo;
}
@Inject
public void setRoleMapping(@Named("X509CertificateRealm-role-mapping") RoleMapping roleMapping) {
this.roleMapping = roleMapping;
}
@Inject
public void setRolePermissionResolver(@Named("X509CertificateRealm-role-permission-resolver") RolePermissionResolver rolePermissionResolver) {
this.rolePermissionResolver = rolePermissionResolver;
}
@Override
public Set<String> getRealmRoles(PrincipalProvider<?> identityPrincipal, Collection<PrincipalProvider<?>> otherPrincipals) {
Set<String> realmRoles = new HashSet<>();
Collection<PrincipalProvider<X509Certificate[]>> certificatePrincipals = Principals.getPrincipalsByType(otherPrincipals,
X509Certificate[].class);
if (certificatePrincipals.isEmpty()) {
return Collections.emptySet();
}
X509Certificate[] certificates = certificatePrincipals.iterator().next().getPrincipal();
for (X509Certificate certificate : certificates) {
String dn = certificate.getIssuerX500Principal().getName(X500Principal.RFC2253);
LdapName ln;
try {
ln = new LdapName(dn);
} catch (InvalidNameException e) {
LOGGER.error("Certificate issuer does not have valid DN : " + dn, e);
continue;
}
for (Rdn rdn : ln.getRdns()) {
if (rdn.getType().equalsIgnoreCase(CN)) {
realmRoles.add(rdn.getValue().toString());
break;
}
}
}
return realmRoles;
}
@Override
public RoleMapping getRoleMapping() {
return roleMapping;
}
@Override
public RolePermissionResolver getRolePermissionResolver() {
return rolePermissionResolver;
}
@Override
public Class<? extends AuthenticationToken> supportedToken() {
return X509CertificateToken.class;
}
}