package org.pac4j.cas.client.direct;
import org.jasig.cas.client.util.CommonUtils;
import org.pac4j.cas.authorization.DefaultCasAuthorizationGenerator;
import org.pac4j.cas.client.CasProxyReceptor;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.cas.credentials.authenticator.CasAuthenticator;
import org.pac4j.core.client.DirectClient;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.TokenCredentials;
import org.pac4j.core.credentials.authenticator.Authenticator;
import org.pac4j.core.credentials.extractor.ParameterExtractor;
import org.pac4j.core.exception.CredentialsException;
import org.pac4j.core.exception.HttpAction;
import org.pac4j.core.exception.TechnicalException;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.util.CommonHelper;
/**
* <p>This class is the direct client to authenticate users on a CAS server for a web application in a stateless way: when trying to access a protected area,
* the user will be redirected to the CAS server for login and then back directly to this originally requested url.</p>
*
* <p>You should generally use the {@link org.pac4j.cas.client.CasClient} instead (this one is very specific and was designed for OAuth / OpenID Connect implementations in the CAS server.</p>
*
* <p>The configuration can be defined via the {@link #configuration} object.</p>
*
* <p>As no session is meant to be created, this client does not handle CAS logout requests.</p>
*
* <p>For proxy support, a {@link CasProxyReceptor} must be defined in the configuration (the corresponding "callback filter" must be enabled)
* and set to the CAS configuration of this client. In that case, a {@link org.pac4j.cas.profile.CasProxyProfile} will be return
* (instead of a {@link org.pac4j.cas.profile.CasProfile}) to be able to request proxy tickets.</p>
*
* @author Jerome Leleu
* @since 1.9.2
*/
public class DirectCasClient extends DirectClient<TokenCredentials, CommonProfile> {
private CasConfiguration configuration;
public DirectCasClient() { }
public DirectCasClient(final CasConfiguration casConfiguration) {
this.configuration = casConfiguration;
}
@Override
protected TokenCredentials retrieveCredentials(final WebContext context) throws HttpAction {
init(context);
try {
String currentUrl = configuration.computeFinalUrl(context.getFullRequestURL(), context);
final String loginUrl = configuration.computeFinalLoginUrl(context);
final TokenCredentials credentials = getCredentialsExtractor().extract(context);
if (credentials == null) {
// redirect to the login page
final String redirectionUrl = CommonUtils.constructRedirectUrl(loginUrl, CasConfiguration.SERVICE_PARAMETER,
currentUrl, configuration.isRenew(), false);
logger.debug("redirectionUrl: {}", redirectionUrl);
throw HttpAction.redirect("no ticket -> force redirect to login page", context, redirectionUrl);
}
// clean url from ticket parameter
currentUrl = CommonHelper.substringBefore(currentUrl, "?" + CasConfiguration.TICKET_PARAMETER + "=");
currentUrl = CommonHelper.substringBefore(currentUrl, "&" + CasConfiguration.TICKET_PARAMETER + "=");
final CasAuthenticator casAuthenticator = new CasAuthenticator(configuration, currentUrl);
casAuthenticator.init(context);
casAuthenticator.validate(credentials, context);
return credentials;
} catch (CredentialsException e) {
logger.error("Failed to retrieve or validate CAS credentials", e);
return null;
}
}
@Override
protected void clientInit(final WebContext context) {
CommonHelper.assertNotNull("configuration", this.configuration);
CommonHelper.assertTrue(!configuration.isGateway(), "the DirectCasClient can not support gateway to avoid infinite loops");
configuration.init(context);
defaultCredentialsExtractor(new ParameterExtractor(CasConfiguration.TICKET_PARAMETER, true, false, getName()));
// only a fake one for the initialization as we will build a new one with the current url for each request
super.defaultAuthenticator(new CasAuthenticator(configuration, "fake"));
addAuthorizationGenerator(new DefaultCasAuthorizationGenerator<>());
}
public CasConfiguration getConfiguration() {
return configuration;
}
public void setConfiguration(final CasConfiguration configuration) {
this.configuration = configuration;
}
@Override
protected void defaultAuthenticator(final Authenticator authenticator) {
throw new TechnicalException("You can not set an Authenticator for the DirectCasClient at startup. A new CasAuthenticator is automatically created for each request");
}
@Override
public String toString() {
return CommonHelper.toString(this.getClass(), "configuration", this.configuration);
}
}