package org.atricore.idbus.capabilities.oauth2.main.emitter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.atricore.idbus.capabilities.oauth2.common.*;
import org.atricore.idbus.capabilities.oauth2.common.util.JasonUtils;
import org.atricore.idbus.capabilities.sts.main.AbstractSecurityTokenEmitter;
import org.atricore.idbus.capabilities.sts.main.SecurityTokenEmissionException;
import org.atricore.idbus.capabilities.sts.main.SecurityTokenProcessingContext;
import org.atricore.idbus.capabilities.sts.main.WSTConstants;
import org.atricore.idbus.common.oauth._2_0.protocol.OAuthAccessTokenType;
import org.atricore.idbus.common.oauth._2_0.protocol.ObjectFactory;
import org.atricore.idbus.kernel.main.authn.*;
import org.atricore.idbus.kernel.main.store.SSOIdentityManager;
import org.atricore.idbus.kernel.planning.IdentityArtifact;
import org.oasis_open.docs.wss._2004._01.oasis_200401_wss_wssecurity_secext_1_0.UsernameTokenType;
import javax.security.auth.Subject;
import javax.xml.namespace.QName;
import java.io.IOException;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
/**
* @author <a href=mailto:sgonzalez@atricore.org>Sebastian Gonzalez Oyuela</a>
*/
public class OAuth2AccessTokenEmitter extends AbstractSecurityTokenEmitter {
private static final Log logger = LogFactory.getLog(OAuth2AccessTokenEmitter.class);
private SSOIdentityManager identityManager;
private TokenSigner tokenSigner;
private TokenEncrypter tokenEncrypter;
private Random randomGenerator = new Random();
// Default to 30 days
private long rememberMeTokenValidityMins = 60L * 24L * 30L;
// Default to 10 minutes
private long tokenValiditySecs = 60L * 10L;
@Override
public boolean isTargetedEmitter(SecurityTokenProcessingContext context, Object requestToken, String tokenType) {
return context.getProperty(WSTConstants.SUBJECT_PROP) != null &&
WSTConstants.WST_OAUTH2_TOKEN_TYPE.equals(tokenType);
}
@Override
public SecurityToken emit(SecurityTokenProcessingContext context,
Object requestToken,
String tokenType) throws SecurityTokenEmissionException {
try {
Subject subject = (Subject) context.getProperty(WSTConstants.SUBJECT_PROP);
// Resolve subject, using configured identity source
subject = resolveSubject(subject);
if (logger.isTraceEnabled())
logger.trace("Building OAuth2 Token from " + subject);
// Build an access token for the subject,
OAuth2AccessToken token = buildOAuth2AccessToken(subject, requestToken);
OAuth2AccessTokenEnvelope envelope = buildOAuth2AccessTokenEnvelope(token);
String tokenValue = marshalOAuthAccessEnvelopeToken(envelope);
String uuid = super.uuidGenerator.generateId();
ObjectFactory of = new ObjectFactory();
OAuthAccessTokenType oauthToken = of.createOAuthAccessTokenType();
oauthToken.setTokenType("bearer");
oauthToken.setAccessToken(tokenValue);
oauthToken.setExpiresIn(System.currentTimeMillis() - token.getExpiresOn());
// Create a security token using the OUT artifact content.
SecurityTokenImpl st = new SecurityTokenImpl(uuid,
WSTConstants.WST_OAUTH2_TOKEN_TYPE,
oauthToken,
tokenValue);
// Set token expiration
st.setExpiresOn(token.getExpiresOn());
logger.debug("Created new security token [" + uuid + "] with content " + (oauthToken.getClass().getSimpleName()));
return st;
} catch (IOException e) {
throw new SecurityTokenEmissionException(e);
} catch (OAuth2SignatureException e) {
throw new SecurityTokenEmissionException(e);
} catch (OAuth2EncryptionException e) {
throw new SecurityTokenEmissionException(e);
}
}
protected OAuth2AccessTokenEnvelope buildOAuth2AccessTokenEnvelope(OAuth2AccessToken token) throws IOException, OAuth2EncryptionException, OAuth2SignatureException {
// Build and deflate
String tokenValue = JasonUtils.marshalAccessToken(token);
tokenValue = JasonUtils.deflate(tokenValue, true);
// Encrypt
String encryptAlg = null;
if (tokenEncrypter != null) {
tokenValue = tokenEncrypter.encrypt(tokenValue);
encryptAlg = tokenEncrypter.getEncryptAlg();
}
// Sign
String sigValue = null;
String sigAlg = null;
if (tokenSigner != null) {
sigValue = tokenSigner.signToken(tokenValue);
sigAlg = tokenSigner.getSignAlg();
}
return new OAuth2AccessTokenEnvelope (encryptAlg, sigAlg, sigValue, tokenValue, true);
}
protected OAuth2AccessToken buildOAuth2AccessToken(Subject subject, Object requestToken) {
// User
Set<SSOUser> ssoUsers = subject.getPrincipals(SSOUser.class);
assert ssoUsers.size() == 1;
OAuth2AccessToken at = new OAuth2AccessToken();
SSOUser user = ssoUsers.iterator().next();
at.getClaims().add(new OAuth2Claim(OAuth2ClaimType.USERID.toString(), user.getName()));
// Just a temporary work-around.
at.getClaims().add(new OAuth2Claim(OAuth2ClaimType.UNKNOWN.toString(), "UNKNOWN"));
// Roles
Set<SSORole> ssoRoles = subject.getPrincipals(SSORole.class);
for (SSORole ssoRole : ssoRoles) {
at.getClaims().add(new OAuth2Claim(OAuth2ClaimType.ROLE.toString(), ssoRole.getName()));
}
if (user.getProperties() != null) {
for (SSONameValuePair property : user.getProperties()) {
at.getClaims().add(new OAuth2Claim(OAuth2ClaimType.ATTRIBUTE.toString(), property.getName(), property.getValue()));
}
}
long expiresIn = tokenValiditySecs;
if (requestToken instanceof UsernameTokenType) {
UsernameTokenType ut = (UsernameTokenType) requestToken;
// When the requested token has a remember-me attribute, we must persist the token
String rememberMe = ut.getOtherAttributes().get(new QName(Constants.REMEMBERME_NS));
if (rememberMe != null && Boolean.parseBoolean(rememberMe)) {
// 30 days for remember-me tokens
// Mark the token as used for remember-me.
expiresIn = 1000L * 60L * rememberMeTokenValidityMins;
at.getClaims().add(new OAuth2Claim(OAuth2ClaimType.ATTRIBUTE.name(), Constants.REMEMBERME_NS, "TRUE"));
}
}
at.setExpiresOn(at.getTimeStamp() + expiresIn);
// User properties
// TODO:
// Create some random information, to make every token unique!
at.setTimeStamp(System.currentTimeMillis());
at.setRnd(randomGenerator.nextInt());
return at;
}
/**
* TODO : Use identity planning, and reuse some STS actions!
*
* @param subject
* @return
*/
protected Subject resolveSubject(Subject subject) {
Set<SSOUser> ssoUsers = subject.getPrincipals(SSOUser.class);
Set<SimplePrincipal> simplePrincipals = subject.getPrincipals(SimplePrincipal.class);
if (ssoUsers != null && ssoUsers.size() > 0) {
if (logger.isDebugEnabled())
logger.debug("Emitting token for Subject with SSOUser");
// Build Subject
// s = new Subject(true, s.getPrincipals(), s.getPrivateCredentials(), s.getPublicCredentials());
} else {
try {
// Resolve SSOUser
SimplePrincipal sp = simplePrincipals.iterator().next();
String username = sp.getName();
SSOIdentityManager idMgr = getIdentityManager();
// Obtain SSOUser principal
SSOUser ssoUser = null;
SSORole[] ssoRoles = null;
if (idMgr != null) {
if (logger.isTraceEnabled())
logger.trace("Resolving SSOUser for " + username);
ssoUser = idMgr.findUser(username);
ssoRoles = idMgr.findRolesByUsername(username);
} else {
if (logger.isTraceEnabled())
logger.trace("Not resolving SSOUser for " + username);
ssoUser = new BaseUserImpl(username);
ssoRoles = new BaseRoleImpl[0];
}
Set<Principal> principals = new HashSet<Principal>();
principals.add(ssoUser);
principals.addAll(Arrays.asList(ssoRoles));
// Use existing SSOPolicyEnforcement principals
Set<SSOPolicyEnforcementStatement> ssoPolicies = subject.getPrincipals(SSOPolicyEnforcementStatement.class);
if (ssoPolicies != null) {
if (logger.isDebugEnabled())
logger.debug("Adding " + ssoPolicies.size() + " SSOPolicyEnforcement principals ");
principals.addAll(ssoPolicies);
}
// Build Subject
subject = new Subject(true, principals, subject.getPublicCredentials(), subject.getPrivateCredentials());
} catch (Exception e) {
throw new SecurityTokenEmissionException(e);
}
}
return subject;
}
private String marshalOAuthAccessEnvelopeToken(OAuth2AccessTokenEnvelope envelope) throws IOException {
return JasonUtils.marshalAccessTokenEnvelope(envelope, true);
}
@Override
protected IdentityArtifact createOutArtifact(Object requestToken, String tokenType) {
throw new UnsupportedOperationException("Operation not available");
}
public SSOIdentityManager getIdentityManager() {
return identityManager;
}
public void setIdentityManager(SSOIdentityManager identityManager) {
this.identityManager = identityManager;
}
public TokenSigner getTokenSigner() {
return tokenSigner;
}
public void setTokenSigner(TokenSigner tokenSigner) {
this.tokenSigner = tokenSigner;
}
public TokenEncrypter getTokenEncrypter() {
return tokenEncrypter;
}
public void setTokenEncrypter(TokenEncrypter tokenEncrypter) {
this.tokenEncrypter = tokenEncrypter;
}
public long getRememberMeTokenValidityMins() {
return rememberMeTokenValidityMins;
}
public void setRememberMeTokenValidityMins(long rememberMeTokenValidityMins) {
this.rememberMeTokenValidityMins = rememberMeTokenValidityMins;
}
public long getTokenValiditySecs() {
return tokenValiditySecs;
}
public void setTokenValiditySecs(long tokenValiditySecs) {
this.tokenValiditySecs = tokenValiditySecs;
}
}