package org.pac4j.saml.client;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.encryption.Decrypter;
import org.pac4j.core.client.IndirectClient;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.TechnicalException;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.saml.context.SAML2ContextProvider;
import org.pac4j.saml.context.SAML2MessageContext;
import org.pac4j.saml.context.SAMLContextProvider;
import org.pac4j.saml.credentials.SAML2Credentials;
import org.pac4j.saml.credentials.authenticator.SAML2Authenticator;
import org.pac4j.saml.crypto.CredentialProvider;
import org.pac4j.saml.crypto.DefaultSignatureSigningParametersProvider;
import org.pac4j.saml.crypto.ExplicitSignatureTrustEngineProvider;
import org.pac4j.saml.crypto.KeyStoreCredentialProvider;
import org.pac4j.saml.crypto.KeyStoreDecryptionProvider;
import org.pac4j.saml.crypto.SAML2SignatureTrustEngineProvider;
import org.pac4j.saml.crypto.SignatureSigningParametersProvider;
import org.pac4j.saml.logout.SAML2LogoutActionBuilder;
import org.pac4j.saml.metadata.SAML2IdentityProviderMetadataResolver;
import org.pac4j.saml.metadata.SAML2MetadataResolver;
import org.pac4j.saml.metadata.SAML2ServiceProviderMetadataResolver;
import org.pac4j.saml.profile.SAML2Profile;
import org.pac4j.saml.redirect.SAML2RedirectActionBuilder;
import org.pac4j.saml.sso.SAML2ProfileHandler;
import org.pac4j.saml.sso.SAML2ResponseValidator;
import org.pac4j.saml.sso.impl.SAML2DefaultResponseValidator;
import org.pac4j.saml.sso.impl.SAML2WebSSOMessageReceiver;
import org.pac4j.saml.sso.impl.SAML2WebSSOMessageSender;
import org.pac4j.saml.sso.impl.SAML2WebSSOProfileHandler;
import org.pac4j.saml.util.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* This class is the client to authenticate users with a SAML2 Identity Provider. This implementation relies on the Web
* Browser SSO profile with HTTP-POST binding. (http://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf).
*
* @author Michael Remond
* @author Misagh Moayyed
* @author Ruochao Zheng
* @since 1.5.0
*/
public class SAML2Client extends IndirectClient<SAML2Credentials, SAML2Profile> {
public static final String SAML_RELAY_STATE_ATTRIBUTE = "samlRelayState";
protected CredentialProvider credentialProvider;
protected SAMLContextProvider contextProvider;
protected SignatureSigningParametersProvider signatureSigningParametersProvider;
protected SAML2ProfileHandler<AuthnRequest> profileHandler;
protected SAML2ResponseValidator responseValidator;
protected SAML2SignatureTrustEngineProvider signatureTrustEngineProvider;
protected SAML2MetadataResolver idpMetadataResolver;
protected SAML2MetadataResolver spMetadataResolver;
protected Decrypter decrypter;
protected SAML2ClientConfiguration configuration;
static {
CommonHelper.assertNotNull("parserPool", Configuration.getParserPool());
CommonHelper.assertNotNull("marshallerFactory", Configuration.getMarshallerFactory());
CommonHelper.assertNotNull("unmarshallerFactory", Configuration.getUnmarshallerFactory());
CommonHelper.assertNotNull("builderFactory", Configuration.getBuilderFactory());
}
public SAML2Client() { }
public SAML2Client(final SAML2ClientConfiguration configuration) {
this.configuration = configuration;
}
@Override
protected void clientInit(final WebContext context) {
CommonHelper.assertNotNull("configuration", this.configuration);
// First of all, initialize the configuration. It may dynamically load some properties, if it is not a static one.
this.configuration.init(getName(), context);
initCredentialProvider();
initDecrypter();
initSignatureSigningParametersProvider();
final MetadataResolver metadataManager = initChainingMetadataResolver(
initIdentityProviderMetadataResolver(),
initServiceProviderMetadataResolver(context));
initSAMLContextProvider(metadataManager);
initSignatureTrustEngineProvider(metadataManager);
initSAMLResponseValidator();
initSAMLProfileHandler();
defaultRedirectActionBuilder(new SAML2RedirectActionBuilder(this));
defaultCredentialsExtractor(ctx -> {
final SAML2MessageContext samlContext = this.contextProvider.buildContext(ctx);
final SAML2Credentials credentials = (SAML2Credentials) this.profileHandler.receive(samlContext);
// The profile handler sets a hard-coded client name, we need the real one.
credentials.setClientName(getName());
return credentials;
});
defaultAuthenticator(new SAML2Authenticator());
defaultLogoutActionBuilder(new SAML2LogoutActionBuilder<>(this));
}
protected void initSAMLProfileHandler() {
this.profileHandler = new SAML2WebSSOProfileHandler(
new SAML2WebSSOMessageSender(this.signatureSigningParametersProvider,
this.configuration.getDestinationBindingType(),
this.configuration.isForceSignRedirectBindingAuthnRequest()),
new SAML2WebSSOMessageReceiver(this.responseValidator));
}
protected void initSAMLResponseValidator() {
// Build the SAML response validator
this.responseValidator = new SAML2DefaultResponseValidator(
this.signatureTrustEngineProvider,
this.decrypter,
this.configuration.getMaximumAuthenticationLifetime(),
this.configuration.getWantsAssertionsSigned());
}
protected void initSignatureTrustEngineProvider(final MetadataResolver metadataManager) {
// Build provider for digital signature validation and encryption
this.signatureTrustEngineProvider = new ExplicitSignatureTrustEngineProvider(metadataManager);
}
protected void initSAMLContextProvider(final MetadataResolver metadataManager) {
// Build the contextProvider
this.contextProvider = new SAML2ContextProvider(metadataManager,
this.idpMetadataResolver, this.spMetadataResolver,
this.configuration.getSamlMessageStorageFactory());
}
protected MetadataResolver initServiceProviderMetadataResolver(final WebContext context) {
this.spMetadataResolver = new SAML2ServiceProviderMetadataResolver(this.configuration,
computeFinalCallbackUrl(context),
this.credentialProvider);
return this.spMetadataResolver.resolve();
}
protected MetadataResolver initIdentityProviderMetadataResolver() {
this.idpMetadataResolver = new SAML2IdentityProviderMetadataResolver(this.configuration);
return this.idpMetadataResolver.resolve();
}
protected void initCredentialProvider() {
this.credentialProvider = new KeyStoreCredentialProvider(this.configuration);
}
protected void initDecrypter() {
this.decrypter = new KeyStoreDecryptionProvider(this.credentialProvider).build();
}
protected void initSignatureSigningParametersProvider() {
this.signatureSigningParametersProvider = new DefaultSignatureSigningParametersProvider(
this.credentialProvider, this.configuration);
}
protected ChainingMetadataResolver initChainingMetadataResolver(final MetadataResolver idpMetadataProvider,
final MetadataResolver spMetadataProvider) {
final ChainingMetadataResolver metadataManager = new ChainingMetadataResolver();
metadataManager.setId(ChainingMetadataResolver.class.getCanonicalName());
try {
final List<MetadataResolver> list = new ArrayList<>();
list.add(idpMetadataProvider);
list.add(spMetadataProvider);
metadataManager.setResolvers(list);
metadataManager.initialize();
} catch (final ResolverException e) {
throw new TechnicalException("Error adding idp or sp metadatas to manager", e);
} catch (final ComponentInitializationException e) {
throw new TechnicalException("Error initializing manager", e);
}
return metadataManager;
}
public String getStateParameter(final WebContext webContext) {
final String relayState = (String) webContext.getSessionAttribute(SAML_RELAY_STATE_ATTRIBUTE);
// clean from session after retrieving it
webContext.setSessionAttribute(SAML_RELAY_STATE_ATTRIBUTE, "");
return (relayState == null) ? computeFinalCallbackUrl(webContext) : relayState;
}
public final SAML2ResponseValidator getResponseValidator() {
return this.responseValidator;
}
public final SAML2MetadataResolver getServiceProviderMetadataResolver() {
return this.spMetadataResolver;
}
public final SAML2MetadataResolver getIdentityProviderMetadataResolver() {
return this.idpMetadataResolver;
}
public final String getIdentityProviderResolvedEntityId() {
return this.idpMetadataResolver.getEntityId();
}
public final String getServiceProviderResolvedEntityId() {
return this.spMetadataResolver.getEntityId();
}
public void setConfiguration(final SAML2ClientConfiguration configuration) {
this.configuration = configuration;
}
public final SAML2ClientConfiguration getConfiguration() {
return this.configuration;
}
public SAMLContextProvider getContextProvider() {
return contextProvider;
}
public SAML2ProfileHandler<AuthnRequest> getProfileHandler() {
return profileHandler;
}
public SignatureSigningParametersProvider getSignatureSigningParametersProvider() {
return signatureSigningParametersProvider;
}
public SAML2SignatureTrustEngineProvider getSignatureTrustEngineProvider() {
return signatureTrustEngineProvider;
}
}