package org.apereo.cas.oidc.web.controllers;
import com.google.common.base.Throwables;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apereo.cas.authentication.Authentication;
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.oidc.OidcConstants;
import org.apereo.cas.oidc.token.OidcIdTokenGeneratorService;
import org.apereo.cas.services.OidcRegisteredService;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.support.oauth.OAuth20ResponseTypes;
import org.apereo.cas.support.oauth.OAuth20Constants;
import org.apereo.cas.support.oauth.authenticator.OAuth20CasAuthenticationBuilder;
import org.apereo.cas.support.oauth.profile.OAuth20ProfileScopeToAttributesFilter;
import org.apereo.cas.support.oauth.services.OAuthRegisteredService;
import org.apereo.cas.support.oauth.util.OAuth20Utils;
import org.apereo.cas.support.oauth.validator.OAuth20Validator;
import org.apereo.cas.support.oauth.web.endpoints.OAuth20AuthorizeEndpointController;
import org.apereo.cas.support.oauth.web.response.accesstoken.ext.AccessTokenRequestDataHolder;
import org.apereo.cas.support.oauth.web.views.ConsentApprovalViewResolver;
import org.apereo.cas.ticket.TicketGrantingTicket;
import org.apereo.cas.ticket.accesstoken.AccessToken;
import org.apereo.cas.ticket.accesstoken.AccessTokenFactory;
import org.apereo.cas.ticket.code.OAuthCodeFactory;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.web.support.CookieRetrievingCookieGenerator;
import org.apereo.cas.web.support.CookieUtils;
import org.pac4j.core.context.J2EContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* This is {@link OidcAuthorizeEndpointController}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public class OidcAuthorizeEndpointController extends OAuth20AuthorizeEndpointController {
private static final Logger LOGGER = LoggerFactory.getLogger(OidcAuthorizeEndpointController.class);
private final OidcIdTokenGeneratorService idTokenGenerator;
public OidcAuthorizeEndpointController(final ServicesManager servicesManager,
final TicketRegistry ticketRegistry,
final OAuth20Validator validator,
final AccessTokenFactory accessTokenFactory,
final PrincipalFactory principalFactory,
final ServiceFactory<WebApplicationService> webApplicationServiceServiceFactory,
final OAuthCodeFactory oAuthCodeFactory,
final ConsentApprovalViewResolver consentApprovalViewResolver,
final OidcIdTokenGeneratorService idTokenGenerator,
final OAuth20ProfileScopeToAttributesFilter scopeToAttributesFilter,
final CasConfigurationProperties casProperties,
final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator,
final OAuth20CasAuthenticationBuilder authenticationBuilder) {
super(servicesManager, ticketRegistry, validator, accessTokenFactory, principalFactory,
webApplicationServiceServiceFactory, oAuthCodeFactory, consentApprovalViewResolver,
scopeToAttributesFilter, casProperties, ticketGrantingTicketCookieGenerator,
authenticationBuilder);
this.idTokenGenerator = idTokenGenerator;
}
@GetMapping(value = '/' + OidcConstants.BASE_OIDC_URL + '/' + OAuth20Constants.AUTHORIZE_URL)
@Override
public ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
final Collection<String> scopes = OAuth20Utils.getRequestedScopes(request);
if (scopes.isEmpty() || !scopes.contains(OidcConstants.OPENID)) {
LOGGER.warn("Provided scopes [{}] are undefined by OpenID Connect, which requires that scope [{}] MUST be specified, "
+ "or the behavior is unspecified. CAS MAY allow this request to be processed for now.",
scopes, OidcConstants.OPENID);
}
return super.handleRequest(request, response);
}
@Override
protected OAuthRegisteredService getRegisteredServiceByClientId(final String clientId) {
final OAuthRegisteredService service = super.getRegisteredServiceByClientId(clientId);
scopeToAttributesFilter.reconcile(service);
return service;
}
@Override
protected String buildCallbackUrlForTokenResponseType(final J2EContext context, final Authentication authentication,
final Service service, final String redirectUri,
final String responseType,
final String clientId) {
if (!OAuth20Utils.isResponseType(responseType, OAuth20ResponseTypes.IDTOKEN_TOKEN)) {
return super.buildCallbackUrlForTokenResponseType(context, authentication, service,
redirectUri, responseType, clientId);
}
LOGGER.debug("Handling callback for response type [{}]", responseType);
final TicketGrantingTicket ticketGrantingTicket = CookieUtils.getTicketGrantingTicketFromRequest(
ticketGrantingTicketCookieGenerator, this.ticketRegistry, context.getRequest());
return buildCallbackUrlForImplicitTokenResponseType(context, authentication,
service, redirectUri, clientId, OAuth20ResponseTypes.IDTOKEN_TOKEN, ticketGrantingTicket);
}
private String buildCallbackUrlForImplicitTokenResponseType(final J2EContext context,
final Authentication authentication,
final Service service,
final String redirectUri,
final String clientId,
final OAuth20ResponseTypes responseType,
final TicketGrantingTicket ticketGrantingTicket) {
try {
final OidcRegisteredService oidcService = (OidcRegisteredService)
OAuth20Utils.getRegisteredOAuthService(this.servicesManager, clientId);
final AccessTokenRequestDataHolder holder = new AccessTokenRequestDataHolder(service, authentication, oidcService, ticketGrantingTicket);
final AccessToken accessToken = generateAccessToken(holder);
LOGGER.debug("Generated OAuth access token: [{}]", accessToken);
final long timeout = casProperties.getTicket().getTgt().getTimeToKillInSeconds();
final String idToken = this.idTokenGenerator.generate(context.getRequest(),
context.getResponse(),
accessToken, timeout, responseType, oidcService);
LOGGER.debug("Generated id token [{}]", idToken);
final List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair(OidcConstants.ID_TOKEN, idToken));
return buildCallbackUrlResponseType(authentication, service, redirectUri, accessToken, params);
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
}