package org.apereo.cas.support.oauth.authenticator;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.CasProtocolConstants;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.authentication.AuthenticationBuilder;
import org.apereo.cas.authentication.BasicCredentialMetaData;
import org.apereo.cas.authentication.BasicIdentifiableCredential;
import org.apereo.cas.authentication.CredentialMetaData;
import org.apereo.cas.authentication.DefaultAuthenticationBuilder;
import org.apereo.cas.authentication.DefaultHandlerResult;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.authentication.principal.ServiceFactory;
import org.apereo.cas.authentication.principal.WebApplicationService;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.support.oauth.OAuth20Constants;
import org.apereo.cas.support.oauth.profile.OAuth20ProfileScopeToAttributesFilter;
import org.apereo.cas.support.oauth.services.OAuthRegisteredService;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.profile.UserProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.time.ZonedDateTime;
import java.util.ArrayList;
/**
* This is {@link OAuth20CasAuthenticationBuilder}.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
public class OAuth20CasAuthenticationBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(OAuth20CasAuthenticationBuilder.class);
/**
* Collection of CAS settings.
*/
protected final CasConfigurationProperties casProperties;
/**
* The Principal factory.
*/
protected final PrincipalFactory principalFactory;
/**
* The Web application service service factory.
*/
protected final ServiceFactory<WebApplicationService> webApplicationServiceServiceFactory;
/**
* Convert profile scopes to attributes.
*/
protected final OAuth20ProfileScopeToAttributesFilter scopeToAttributesFilter;
public OAuth20CasAuthenticationBuilder(final PrincipalFactory principalFactory,
final ServiceFactory<WebApplicationService> webApplicationServiceServiceFactory,
final OAuth20ProfileScopeToAttributesFilter scopeToAttributesFilter,
final CasConfigurationProperties casProperties) {
this.principalFactory = principalFactory;
this.webApplicationServiceServiceFactory = webApplicationServiceServiceFactory;
this.scopeToAttributesFilter = scopeToAttributesFilter;
this.casProperties = casProperties;
}
/**
* Build service.
*
* @param registeredService the registered service
* @param context the context
* @param useServiceHeader the use service header
* @return the service
*/
public Service buildService(final OAuthRegisteredService registeredService, final J2EContext context, final boolean useServiceHeader) {
String id = null;
if (useServiceHeader) {
id = context.getRequestHeader(CasProtocolConstants.PARAMETER_SERVICE);
if (StringUtils.isBlank(id)) {
id = context.getRequestHeader("X-".concat(CasProtocolConstants.PARAMETER_SERVICE));
}
LOGGER.debug("Located service based on request header is [{}]", id);
}
if (StringUtils.isBlank(id)) {
id = registeredService.getClientId();
}
return webApplicationServiceServiceFactory.createService(id);
}
/**
* Create an authentication from a user profile.
*
* @param profile the given user profile
* @param registeredService the registered service
* @param context the context
* @param service the service
* @return the built authentication
*/
public Authentication build(final UserProfile profile,
final OAuthRegisteredService registeredService,
final J2EContext context,
final Service service) {
final Principal newPrincipal =
this.scopeToAttributesFilter.filter(service,
this.principalFactory.createPrincipal(profile.getId(), profile.getAttributes()),
registeredService,
context);
LOGGER.debug("Created final principal [{}] after filtering attributes based on [{}]", newPrincipal, registeredService);
final String authenticator = profile.getClass().getCanonicalName();
final CredentialMetaData metadata = new BasicCredentialMetaData(new BasicIdentifiableCredential(profile.getId()));
final HandlerResult handlerResult = new DefaultHandlerResult(authenticator, metadata, newPrincipal, new ArrayList<>());
final String state = StringUtils.defaultIfBlank(context.getRequestParameter(OAuth20Constants.STATE), StringUtils.EMPTY);
final String nonce = StringUtils.defaultIfBlank(context.getRequestParameter(OAuth20Constants.NONCE), StringUtils.EMPTY);
LOGGER.debug("OAuth [{}] is [{}], and [{}] is [{}]", OAuth20Constants.STATE, state, OAuth20Constants.NONCE, nonce);
/**
* pac4j UserProfile.getPermissions() and getRoles() returns UnmodifiableSet which Jackson Serializer
* happily serializes to json but unable to deserialize.
* We have to wrap it to HashSet to avoid such problem
*/
final AuthenticationBuilder bldr = DefaultAuthenticationBuilder.newInstance()
.addAttribute("permissions", new HashSet(profile.getPermissions()))
.addAttribute("roles", new HashSet(profile.getRoles()))
.addAttribute(OAuth20Constants.STATE, state)
.addAttribute(OAuth20Constants.NONCE, nonce)
.addCredential(metadata)
.setPrincipal(newPrincipal)
.setAuthenticationDate(ZonedDateTime.now())
.addSuccess(profile.getClass().getCanonicalName(), handlerResult);
// Add "other" profile attributes as authentication attributes.
if (casProperties.getAuthn().getOauth().getAccessToken().isReleaseProtocolAttributes()) {
profile.getAttributes().forEach((k, v) -> {
if (!newPrincipal.getAttributes().containsKey(k)) {
LOGGER.debug("Added attribute [{}] with value [{}] to the authentication", k, v);
bldr.addAttribute(k, v);
} else {
LOGGER.debug("Skipped over attribute [{}] since it's already contained by the principal", k);
}
});
}
return bldr.build();
}
}