/**
*
*/
package org.mitre.openid.connect.binder.service;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.naming.AuthenticationNotSupportedException;
import org.mitre.openid.connect.binder.authentication.MultipleIdentityAuthentication;
import org.mitre.openid.connect.binder.model.SingleIdentity;
import org.mitre.openid.connect.binder.model.MultipleIdentity;
import org.mitre.openid.connect.binder.repository.SingleIdentityRepository;
import org.mitre.openid.connect.binder.repository.MultipleIdentityRepository;
import org.mitre.openid.connect.model.OIDCAuthenticationToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
/**
* @author wkim
*
*/
@Service
public class IdentityServiceDefault implements IdentityService {
@Autowired
private SingleIdentityRepository singleIdentityRepository;
@Autowired
private MultipleIdentityRepository multipleIdentityRepository;
private final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* This bind also binds any identities that were previously binded to the actively logged in identities.
*/
@Override
public MultipleIdentity bind() throws AuthenticationNotSupportedException {
MultipleIdentity multipleIdentity = new MultipleIdentity();
Set<SingleIdentity> tempIdentities = new HashSet<SingleIdentity>();
for (OIDCAuthenticationToken token : getCurrentTokens()) {
// find and bind previously binded identities and delete old multiple identity
MultipleIdentity oldMultiple = getMultipleBySubjectIssuer(token.getSub(), token.getIssuer());
if (oldMultiple != null) {
tempIdentities.addAll(oldMultiple.getIdentities());
multipleIdentityRepository.delete(oldMultiple);
}
SingleIdentity singleIdentity = convertTokenIdentity(token);
// add to new one
tempIdentities.add(singleIdentity);
}
// re-'set' the set :P
Set<SingleIdentity> identities = new HashSet<SingleIdentity>();
identities.addAll(tempIdentities);
multipleIdentity.setIdentities(identities);
log.info("Following identities bound successfully: " + multipleIdentity + ".");
return saveMultipleIdentity(multipleIdentity);
}
@Override
public MultipleIdentity unbind(MultipleIdentity multipleIdentity, SingleIdentity singleIdentity) {
if (multipleIdentity == null || multipleIdentity.getIdentities() == null || multipleIdentity.getIdentities().isEmpty()) {
log.info("Unbind failed: no bound identities");
return multipleIdentity;
}
Set<SingleIdentity> identities = multipleIdentity.getIdentities();
identities.remove(singleIdentity);
multipleIdentity.setIdentities(identities);
singleIdentityRepository.delete(singleIdentity);
log.info("Identity "+ singleIdentity + " unbound successfully from " + multipleIdentity + ".");
return multipleIdentityRepository.save(multipleIdentity);
}
@Override
public MultipleIdentity unbindBySubjectIssuer(MultipleIdentity multipleIdentity, String subject, String issuer) {
return unbind(multipleIdentity, getSingleBySubjectIssuer(subject, issuer));
}
@Override
public SingleIdentity getSingleBySubjectIssuer(String subject, String issuer) {
return singleIdentityRepository.findBySubjectAndIssuer(subject, issuer);
}
@Override
public MultipleIdentity getMultipleBySubjectIssuer(String subject, String issuer) {
// TODO do this querying logic at the repository layer instead so that we dont have to query for all the identities
SingleIdentity single = getSingleBySubjectIssuer(subject, issuer);
if (single == null) {
log.info("Unknown subject: " + subject + " and issuer: " + issuer + ".");
return null;
}
Set<MultipleIdentity> allMultiples = Sets.newHashSet(multipleIdentityRepository.findAll());
for (MultipleIdentity multiple : allMultiples) {
if (multiple.getIdentities() != null && multiple.getIdentities().contains(single)) {
return multiple;
}
}
return null;
}
@Override
public SingleIdentity saveSingleIdentity(SingleIdentity singleIdentity) {
if (singleIdentity == null) { // return early for null
return null;
}
// check to see if subject/issuer already exists
SingleIdentity updatedIdentity = getSingleBySubjectIssuer(singleIdentity.getSubject(), singleIdentity.getIssuer());
if (updatedIdentity == null) { // just save it right away
return singleIdentityRepository.save(singleIdentity);
}
// else, update the old one
updatedIdentity.setFirstUsed(singleIdentity.getFirstUsed());
updatedIdentity.setLastUsed(singleIdentity.getLastUsed());
updatedIdentity.setUserInfoJsonString(singleIdentity.getUserInfoJsonString());
return singleIdentityRepository.save(updatedIdentity);
}
@Override
public MultipleIdentity saveMultipleIdentity(MultipleIdentity multipleIdentity) {
return multipleIdentityRepository.save(multipleIdentity);
}
@Override
public SingleIdentity convertTokenIdentity(OIDCAuthenticationToken token) {
SingleIdentity singleIdentity = getSingleBySubjectIssuer(token.getSub(), token.getIssuer());
// save identity information
if (singleIdentity == null) {
singleIdentity = new SingleIdentity();
singleIdentity.setSubject(token.getSub());
singleIdentity.setIssuer(token.getIssuer());
singleIdentity.setFirstUsed(new Date());
}
singleIdentity.setUserInfoJsonString( (token.getUserInfo() == null) ? null : token.getUserInfo().toJson().toString() ); // update user info every time
singleIdentity.setLastUsed(new Date());
return singleIdentity;
}
@Override
public MultipleIdentity getCurrentMultiple() {
// TODO: SRSLY?
Authentication authN = SecurityContextHolder.getContext().getAuthentication();
Set<OIDCAuthenticationToken> tokens = ((MultipleIdentityAuthentication) authN).getTokens();
OIDCAuthenticationToken token = Iterables.getFirst(tokens, null);
if (token == null) {
throw new IllegalStateException("OIDC Authentication must be present.");
}
return getMultipleBySubjectIssuer(token.getSub(), token.getIssuer());
}
/**
* Gets all the OIDC Tokens from the current Security Context.
*
* @return
* @throws AuthenticationNotSupportedException
*/
private Set<OIDCAuthenticationToken> getCurrentTokens() throws AuthenticationNotSupportedException {
Authentication authN = SecurityContextHolder.getContext().getAuthentication();
if ( !(authN instanceof MultipleIdentityAuthentication) ) {
throw new AuthenticationNotSupportedException("Authentication needs to be of type MultipleIdentityAuthentication but was: " + authN.getClass() + ".");
}
MultipleIdentityAuthentication multiAuth = (MultipleIdentityAuthentication) authN;
return multiAuth.getTokens();
}
/**
* Gets the latest token from the current Security Context.
*
* @return
* @throws AuthenticationNotSupportedException
*/
private OIDCAuthenticationToken getNewToken() throws AuthenticationNotSupportedException {
Authentication authN = SecurityContextHolder.getContext().getAuthentication();
if ( !(authN instanceof MultipleIdentityAuthentication) ) {
throw new AuthenticationNotSupportedException("Authentication needs to be of type MultipleIdentityAuthentication but was: " + authN.getClass() + ".");
}
MultipleIdentityAuthentication multiAuth = (MultipleIdentityAuthentication) authN;
return multiAuth.getNewToken();
}
@Override
public MultipleIdentity getPreexistingMultiple() {
Set<OIDCAuthenticationToken> tokens = Sets.newHashSet();
try {
tokens = Sets.newHashSet(getCurrentTokens());
tokens.remove(getNewToken());
} catch (AuthenticationNotSupportedException e) {
log.error("Failed to get preexisting MultipleIdentity: authentication not supported");
log.debug("Failed to get preexisting MultipleIdentity", e);
}
if (tokens.isEmpty()) {
log.info("No preexisting MultipleIdentity.");
return null;
} else {
OIDCAuthenticationToken token = Iterables.getFirst(tokens, null);
return getMultipleBySubjectIssuer(token.getSub(), token.getIssuer());
}
}
@Override
public MultipleIdentity getNewMultiple() {
OIDCAuthenticationToken token = null;
try {
token = getNewToken();
} catch (AuthenticationNotSupportedException e) {
log.error("Failed to get new MultipleIdentity: authentication not supported");
log.debug("Failed to get new MultipleIdentity", e);
}
return token == null ? null : getMultipleBySubjectIssuer(token.getSub(), token.getIssuer());
}
@Override
public Set<SingleIdentity> getAllIdentities() {
return Sets.newHashSet(singleIdentityRepository.findAll());
}
/**
* Only unbinds identities that aren't currently logged in to the Identity Binding Service.
*/
@Override
public MultipleIdentity unbindAll(MultipleIdentity multipleIdentity) {
MultipleIdentity newMultipleIdentity = multipleIdentity;
if( multipleIdentity != null ) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if(auth instanceof MultipleIdentityAuthentication) {
MultipleIdentityAuthentication multiAuth = (MultipleIdentityAuthentication)auth;
Set<SingleIdentity> singleIdentitiesToBeRemoved = new HashSet<>();
for (SingleIdentity single : multipleIdentity.getIdentities()) {
if( !multiAuth.containsIssSubPair(single.getIssuer(), single.getSubject()) ) {
singleIdentitiesToBeRemoved.add(single);
} else { // else TODO error or notice if accounts were logged in so couldnt be unbound?
log.warn("Identity " + single + " is currently logged in and cannot be unbound." );
}
}
for (SingleIdentity single : singleIdentitiesToBeRemoved) {
newMultipleIdentity = unbind(multipleIdentity, single);
}
} else {
log.error("Unbind failed: invalid authentication");
}
} else {
log.error("Unbind failed: invalid MultipleIdentity");
}
log.info("Unbind-all operation completed");
return newMultipleIdentity;
}
}