package org.apereo.cas.oidc.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apereo.cas.CasProtocolConstants;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.oidc.OidcConstants;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.web.support.CookieRetrievingCookieGenerator;
import org.apereo.cas.web.support.WebUtils;
import org.jasig.cas.client.util.URIBuilder;
import org.pac4j.cas.client.CasClient;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.profile.ProfileManager;
import org.pac4j.core.profile.UserProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* This is {@link OidcAuthorizationRequestSupport}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public class OidcAuthorizationRequestSupport {
private static final Logger LOGGER = LoggerFactory.getLogger(OidcAuthorizationRequestSupport.class);
private final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator;
private final TicketRegistrySupport ticketRegistrySupport;
public OidcAuthorizationRequestSupport(final CookieRetrievingCookieGenerator tgtCookieGenerator,
final TicketRegistrySupport ticketRegistrySupport) {
this.ticketGrantingTicketCookieGenerator = tgtCookieGenerator;
this.ticketRegistrySupport = ticketRegistrySupport;
}
/**
* Gets oidc prompt from authorization request.
*
* @param url the url
* @return the oidc prompt from authorization request
*/
public static Set<String> getOidcPromptFromAuthorizationRequest(final String url) {
Assert.notNull(url, "URL cannot be null");
return new URIBuilder(url).getQueryParams().stream()
.filter(p -> OidcConstants.PROMPT.equals(p.getName()))
.map(param -> param.getValue().split(" "))
.flatMap(Arrays::stream)
.collect(Collectors.toSet());
}
/**
* Gets oidc prompt from authorization request.
*
* @param context the context
* @return the oidc prompt from authorization request
*/
public static Set<String> getOidcPromptFromAuthorizationRequest(final WebContext context) {
return getOidcPromptFromAuthorizationRequest(context.getFullRequestURL());
}
/**
* Gets oidc max age from authorization request.
*
* @param context the context
* @return the oidc max age from authorization request
*/
public static Optional<Long> getOidcMaxAgeFromAuthorizationRequest(final WebContext context) {
final URIBuilder builderContext = new URIBuilder(context.getFullRequestURL());
final Optional<URIBuilder.BasicNameValuePair> parameter = builderContext.getQueryParams()
.stream().filter(p -> OidcConstants.MAX_AGE.equals(p.getName()))
.findFirst();
if (parameter.isPresent()) {
final long maxAge = NumberUtils.toLong(parameter.get().getValue(), -1);
return Optional.of(maxAge);
}
return Optional.empty();
}
/**
* Is authentication profile available?.
*
* @param context the context
* @return the optional user profile
*/
public static Optional<UserProfile> isAuthenticationProfileAvailable(final WebContext context) {
final ProfileManager manager = WebUtils.getPac4jProfileManager(context);
return manager.get(true);
}
/**
* Is cas authentication available?
*
* @param context the context
* @return the optional authn
*/
public Optional<Authentication> isCasAuthenticationAvailable(final WebContext context) {
final J2EContext j2EContext = (J2EContext) context;
if (j2EContext != null) {
final String tgtId = ticketGrantingTicketCookieGenerator.retrieveCookieValue(j2EContext.getRequest());
if (StringUtils.isNotBlank(tgtId)) {
final Authentication authentication = ticketRegistrySupport.getAuthenticationFrom(tgtId);
if (authentication != null) {
return Optional.of(authentication);
}
}
}
return Optional.empty();
}
/**
* Is cas authentication old for max age authorization request boolean.
*
* @param context the context
* @param authenticationDate the authentication date
* @return true/false
*/
public boolean isCasAuthenticationOldForMaxAgeAuthorizationRequest(final WebContext context,
final ZonedDateTime authenticationDate) {
final Optional<Long> maxAge = getOidcMaxAgeFromAuthorizationRequest(context);
if (maxAge.isPresent() && maxAge.get() > 0) {
final long now = ZonedDateTime.now().toEpochSecond();
final long authTime = authenticationDate.toEpochSecond();
final long diffInSeconds = now - authTime;
if (diffInSeconds > maxAge.get()) {
LOGGER.info("Authentication is too old: [{}] and was created [{}] seconds ago.",
authTime, diffInSeconds);
return true;
}
}
return false;
}
/**
* Is cas authentication old for max age authorization request?
*
* @param context the context
* @param authentication the authentication
* @return true/false
*/
public boolean isCasAuthenticationOldForMaxAgeAuthorizationRequest(final WebContext context,
final Authentication authentication) {
return isCasAuthenticationOldForMaxAgeAuthorizationRequest(context, authentication.getAuthenticationDate());
}
/**
* Is cas authentication old for max age authorization request?
*
* @param context the context
* @param profile the profile
* @return true/false
*/
public boolean isCasAuthenticationOldForMaxAgeAuthorizationRequest(final WebContext context,
final UserProfile profile) {
final Object authTime =
profile.getAttribute(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_AUTHENTICATION_DATE);
if (authTime == null) {
return false;
}
final ZonedDateTime dt = ZonedDateTime.parse(authTime.toString());
return isCasAuthenticationOldForMaxAgeAuthorizationRequest(context, dt);
}
/**
* Configure client for max age authorization request.
* Sets the CAS client to ask for renewed authentication if
* the authn time is too old based on the requested max age.
*
* @param casClient the cas client
* @param context the context
* @param authentication the authentication
*/
public void configureClientForMaxAgeAuthorizationRequest(final CasClient casClient, final WebContext context,
final Authentication authentication) {
if (isCasAuthenticationOldForMaxAgeAuthorizationRequest(context, authentication)) {
casClient.getConfiguration().setRenew(true);
}
}
/**
* Configure client for prompt login authorization request.
*
* @param casClient the cas client
* @param context the context
*/
public static void configureClientForPromptLoginAuthorizationRequest(final CasClient casClient, final WebContext context) {
final Set<String> prompts = getOidcPromptFromAuthorizationRequest(context);
if (prompts.contains(OidcConstants.PROMPT_LOGIN)) {
casClient.getConfiguration().setRenew(true);
}
}
/**
* Configure client for prompt none authorization request.
*
* @param casClient the cas client
* @param context the context
*/
public static void configureClientForPromptNoneAuthorizationRequest(final CasClient casClient, final WebContext context) {
final Set<String> prompts = getOidcPromptFromAuthorizationRequest(context);
if (prompts.contains(OidcConstants.PROMPT_NONE)) {
casClient.getConfiguration().setRenew(false);
casClient.getConfiguration().setGateway(true);
}
}
}