package org.apereo.cas.support.pac4j.config.support.authentication;
import com.github.scribejava.core.model.Verb;
import com.nimbusds.jose.JWSAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.AuthenticationMetaDataPopulator;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.pac4j.Pac4jProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.support.pac4j.authentication.ClientAuthenticationMetaDataPopulator;
import org.apereo.cas.support.pac4j.authentication.handler.support.ClientAuthenticationHandler;
import org.apereo.cas.support.pac4j.web.flow.SAML2ClientLogoutAction;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.pac4j.cas.client.CasClient;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.core.client.BaseClient;
import org.pac4j.core.client.Clients;
import org.pac4j.oauth.client.BitbucketClient;
import org.pac4j.oauth.client.DropBoxClient;
import org.pac4j.oauth.client.FacebookClient;
import org.pac4j.oauth.client.FoursquareClient;
import org.pac4j.oauth.client.GenericOAuth20Client;
import org.pac4j.oauth.client.GitHubClient;
import org.pac4j.oauth.client.Google2Client;
import org.pac4j.oauth.client.LinkedIn2Client;
import org.pac4j.oauth.client.PayPalClient;
import org.pac4j.oauth.client.TwitterClient;
import org.pac4j.oauth.client.WindowsLiveClient;
import org.pac4j.oauth.client.WordPressClient;
import org.pac4j.oauth.client.YahooClient;
import org.pac4j.oidc.client.AzureAdClient;
import org.pac4j.oidc.client.GoogleOidcClient;
import org.pac4j.oidc.client.OidcClient;
import org.pac4j.oidc.config.OidcConfiguration;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.client.SAML2ClientConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.webflow.execution.Action;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This is {@link Pac4jAuthenticationEventExecutionPlanConfiguration}.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
@Configuration("pac4jAuthenticationEventExecutionPlanConfiguration")
public class Pac4jAuthenticationEventExecutionPlanConfiguration implements AuthenticationEventExecutionPlanConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(Pac4jAuthenticationEventExecutionPlanConfiguration.class);
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Autowired
@Qualifier("personDirectoryPrincipalResolver")
private PrincipalResolver personDirectoryPrincipalResolver;
private void configureGithubClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Github github = casProperties.getAuthn().getPac4j().getGithub();
if (StringUtils.isNotBlank(github.getId()) && StringUtils.isNotBlank(github.getSecret())) {
final GitHubClient client = new GitHubClient(github.getId(), github.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureDropboxClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Dropbox db = casProperties.getAuthn().getPac4j().getDropbox();
if (StringUtils.isNotBlank(db.getId()) && StringUtils.isNotBlank(db.getSecret())) {
final DropBoxClient client = new DropBoxClient(db.getId(), db.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureWindowsLiveClient(final Collection<BaseClient> properties) {
final Pac4jProperties.WindowsLive live = casProperties.getAuthn().getPac4j().getWindowsLive();
if (StringUtils.isNotBlank(live.getId()) && StringUtils.isNotBlank(live.getSecret())) {
final WindowsLiveClient client = new WindowsLiveClient(live.getId(), live.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureYahooClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Yahoo yahoo = casProperties.getAuthn().getPac4j().getYahoo();
if (StringUtils.isNotBlank(yahoo.getId()) && StringUtils.isNotBlank(yahoo.getSecret())) {
final YahooClient client = new YahooClient(yahoo.getId(), yahoo.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureFoursquareClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Foursquare foursquare = casProperties.getAuthn().getPac4j().getFoursquare();
if (StringUtils.isNotBlank(foursquare.getId()) && StringUtils.isNotBlank(foursquare.getSecret())) {
final FoursquareClient client = new FoursquareClient(foursquare.getId(), foursquare.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureGoogleClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Google google = casProperties.getAuthn().getPac4j().getGoogle();
final Google2Client client = new Google2Client(google.getId(), google.getSecret());
if (StringUtils.isNotBlank(google.getId()) && StringUtils.isNotBlank(google.getSecret())) {
if (StringUtils.isNotBlank(google.getScope())) {
client.setScope(Google2Client.Google2Scope.valueOf(google.getScope().toUpperCase()));
}
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureFacebookClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Facebook fb = casProperties.getAuthn().getPac4j().getFacebook();
if (StringUtils.isNotBlank(fb.getId()) && StringUtils.isNotBlank(fb.getSecret())) {
final FacebookClient client = new FacebookClient(fb.getId(), fb.getSecret());
if (StringUtils.isNotBlank(fb.getScope())) {
client.setScope(fb.getScope());
}
if (StringUtils.isNotBlank(fb.getFields())) {
client.setFields(fb.getFields());
}
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureLinkedInClient(final Collection<BaseClient> properties) {
final Pac4jProperties.LinkedIn ln = casProperties.getAuthn().getPac4j().getLinkedIn();
if (StringUtils.isNotBlank(ln.getId()) && StringUtils.isNotBlank(ln.getSecret())) {
final LinkedIn2Client client = new LinkedIn2Client(ln.getId(), ln.getSecret());
if (StringUtils.isNotBlank(ln.getScope())) {
client.setScope(ln.getScope());
}
if (StringUtils.isNotBlank(ln.getFields())) {
client.setFields(ln.getFields());
}
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureTwitterClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Twitter twitter = casProperties.getAuthn().getPac4j().getTwitter();
if (StringUtils.isNotBlank(twitter.getId()) && StringUtils.isNotBlank(twitter.getSecret())) {
final TwitterClient client = new TwitterClient(twitter.getId(), twitter.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureWordpressClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Wordpress wp = casProperties.getAuthn().getPac4j().getWordpress();
if (StringUtils.isNotBlank(wp.getId()) && StringUtils.isNotBlank(wp.getSecret())) {
final WordPressClient client = new WordPressClient(wp.getId(), wp.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureBitbucketClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Bitbucket bb = casProperties.getAuthn().getPac4j().getBitbucket();
if (StringUtils.isNotBlank(bb.getId()) && StringUtils.isNotBlank(bb.getSecret())) {
final BitbucketClient client = new BitbucketClient(bb.getId(), bb.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configurePaypalClient(final Collection<BaseClient> properties) {
final Pac4jProperties.Paypal paypal = casProperties.getAuthn().getPac4j().getPaypal();
if (StringUtils.isNotBlank(paypal.getId()) && StringUtils.isNotBlank(paypal.getSecret())) {
final PayPalClient client = new PayPalClient(paypal.getId(), paypal.getSecret());
LOGGER.debug("Created client [{}] with identifier [{}]", client.getName(), client.getKey());
properties.add(client);
}
}
private void configureCasClient(final Collection<BaseClient> properties) {
final AtomicInteger index = new AtomicInteger();
casProperties.getAuthn().getPac4j().getCas()
.stream()
.filter(cas -> StringUtils.isNotBlank(cas.getLoginUrl()))
.forEach(cas -> {
final CasConfiguration cfg = new CasConfiguration(cas.getLoginUrl(), cas.getProtocol());
final CasClient client = new CasClient(cfg);
final int count = index.intValue();
if (count > 0) {
client.setName(client.getClass().getSimpleName() + count);
}
index.incrementAndGet();
LOGGER.debug("Created client [{}]", client);
properties.add(client);
});
}
private void configureSamlClient(final Collection<BaseClient> properties) {
final AtomicInteger index = new AtomicInteger();
casProperties.getAuthn().getPac4j().getSaml()
.stream()
.filter(saml -> StringUtils.isNotBlank(saml.getKeystorePath()) && StringUtils.isNotBlank(saml.getIdentityProviderMetadataPath()))
.forEach(saml -> {
final SAML2ClientConfiguration cfg = new SAML2ClientConfiguration(saml.getKeystorePath(), saml.getKeystorePassword(),
saml.getPrivateKeyPassword(), saml.getIdentityProviderMetadataPath());
cfg.setMaximumAuthenticationLifetime(saml.getMaximumAuthenticationLifetime());
cfg.setServiceProviderEntityId(saml.getServiceProviderEntityId());
cfg.setServiceProviderMetadataPath(saml.getServiceProviderMetadataPath());
cfg.setDestinationBindingType(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
final SAML2Client client = new SAML2Client(cfg);
final int count = index.intValue();
if (count > 0) {
client.setName(client.getClass().getSimpleName() + count);
}
index.incrementAndGet();
LOGGER.debug("Created client [{}]", client);
properties.add(client);
});
}
private void configureOAuth20Client(final Collection<BaseClient> properties) {
final AtomicInteger index = new AtomicInteger();
casProperties.getAuthn().getPac4j().getOauth2()
.stream()
.filter(oauth -> StringUtils.isNotBlank(oauth.getId()) && StringUtils.isNotBlank(oauth.getSecret()))
.forEach(oauth -> {
final GenericOAuth20Client client = new GenericOAuth20Client();
client.setKey(oauth.getId());
client.setSecret(oauth.getSecret());
client.setProfileAttrs(oauth.getProfileAttrs());
client.setProfileNodePath(oauth.getProfilePath());
client.setProfileUrl(oauth.getProfileUrl());
client.setProfileVerb(Verb.valueOf(oauth.getProfileVerb().toUpperCase()));
client.setTokenUrl(oauth.getTokenUrl());
client.setAuthUrl(oauth.getAuthUrl());
client.setCustomParams(oauth.getCustomParams());
final int count = index.intValue();
if (count > 0) {
client.setName(client.getClass().getSimpleName() + count);
}
index.incrementAndGet();
LOGGER.debug("Created client [{}]", client);
properties.add(client);
});
}
private void configureOidcClient(final Collection<BaseClient> properties) {
final AtomicInteger index = new AtomicInteger();
casProperties.getAuthn().getPac4j().getOidc()
.stream()
.filter(oidc -> StringUtils.isNotBlank(oidc.getId()) && StringUtils.isNotBlank(oidc.getSecret()))
.forEach(oidc -> {
final OidcConfiguration cfg = new OidcConfiguration();
if (StringUtils.isNotBlank(oidc.getScope())) {
cfg.setScope(oidc.getScope());
}
cfg.setUseNonce(oidc.isUseNonce());
cfg.setSecret(oidc.getSecret());
cfg.setClientId(oidc.getId());
if (StringUtils.isNotBlank(oidc.getPreferredJwsAlgorithm())) {
cfg.setPreferredJwsAlgorithm(JWSAlgorithm.parse(oidc.getPreferredJwsAlgorithm().toUpperCase()));
}
cfg.setMaxClockSkew(oidc.getMaxClockSkew());
cfg.setDiscoveryURI(oidc.getDiscoveryUri());
cfg.setCustomParams(oidc.getCustomParams());
final OidcClient client;
switch (oidc.getType().toUpperCase()) {
case "GOOGLE":
client = new GoogleOidcClient(cfg);
break;
case "AZURE":
client = new AzureAdClient(cfg);
break;
case "GENERIC":
default:
client = new OidcClient(cfg);
break;
}
final int count = index.intValue();
if (count > 0) {
client.setName(client.getClass().getSimpleName() + count);
}
index.incrementAndGet();
LOGGER.debug("Created client [{}]", client);
properties.add(client);
});
}
@RefreshScope
@Bean
public Clients builtClients() {
final Set<BaseClient> clients = new LinkedHashSet<>();
configureCasClient(clients);
configureFacebookClient(clients);
configureOidcClient(clients);
configureOAuth20Client(clients);
configureSamlClient(clients);
configureTwitterClient(clients);
configureDropboxClient(clients);
configureFoursquareClient(clients);
configureGithubClient(clients);
configureGoogleClient(clients);
configureWindowsLiveClient(clients);
configureYahooClient(clients);
configureLinkedInClient(clients);
configurePaypalClient(clients);
configureWordpressClient(clients);
configureBitbucketClient(clients);
LOGGER.debug("The following clients are built: [{}]", clients);
if (clients.isEmpty()) {
LOGGER.warn("No delegated authentication clients are defined/configured");
}
LOGGER.info("Located and prepared [{}] delegated authentication client(s)", clients.size());
return new Clients(casProperties.getServer().getLoginUrl(), new ArrayList<>(clients));
}
@ConditionalOnMissingBean(name = "clientPrincipalFactory")
@Bean
public PrincipalFactory clientPrincipalFactory() {
return new DefaultPrincipalFactory();
}
@Bean
public AuthenticationMetaDataPopulator clientAuthenticationMetaDataPopulator() {
return new ClientAuthenticationMetaDataPopulator();
}
@Bean
public Action saml2ClientLogoutAction() {
return new SAML2ClientLogoutAction(builtClients());
}
@RefreshScope
@Bean
public AuthenticationHandler clientAuthenticationHandler() {
final ClientAuthenticationHandler h = new ClientAuthenticationHandler(casProperties.getAuthn().getPac4j().getName(), servicesManager,
clientPrincipalFactory(), builtClients());
h.setTypedIdUsed(casProperties.getAuthn().getPac4j().isTypedIdUsed());
return h;
}
@Override
public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) {
if (!builtClients().findAllClients().isEmpty()) {
LOGGER.info("Registering delegated authentication clients...");
plan.registerAuthenticationHandlerWithPrincipalResolver(clientAuthenticationHandler(), personDirectoryPrincipalResolver);
plan.registerMetadataPopulator(clientAuthenticationMetaDataPopulator());
}
}
}