package org.apereo.cas.support.openid.authentication.principal;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.authentication.principal.AbstractWebApplicationServiceResponseBuilder;
import org.apereo.cas.authentication.principal.Response;
import org.apereo.cas.authentication.principal.WebApplicationService;
import org.apereo.cas.support.openid.OpenIdProtocolConstants;
import org.apereo.cas.ticket.AbstractTicketException;
import org.apereo.cas.validation.Assertion;
import org.apereo.cas.web.support.WebUtils;
import org.openid4java.association.Association;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.Message;
import org.openid4java.message.MessageException;
import org.openid4java.message.ParameterList;
import org.openid4java.server.ServerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* Builds responses to Openid authN requests.
*
* @author Misagh Moayyed
* @since 4.2
*/
public class OpenIdServiceResponseBuilder extends AbstractWebApplicationServiceResponseBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdServiceResponseBuilder.class);
private static final long serialVersionUID = -4581238964007702423L;
private ServerManager serverManager;
private CentralAuthenticationService centralAuthenticationService;
private String openIdPrefixUrl;
/**
* Instantiates a new Open id service response builder.
*
* @param openIdPrefixUrl the open id prefix url
* @param serverManager the server manager
* @param centralAuthenticationService the central authentication service
*/
public OpenIdServiceResponseBuilder(final String openIdPrefixUrl,
final ServerManager serverManager,
final CentralAuthenticationService centralAuthenticationService) {
this.openIdPrefixUrl = openIdPrefixUrl;
this.serverManager = serverManager;
this.centralAuthenticationService = centralAuthenticationService;
}
/**
* Generates an Openid response.
* If no ticketId is found, response is negative.
* If we have a ticket id, then we check if we have an association.
* If so, we ask OpenId server manager to generate the answer according with the existing association.
* If not, we send back an answer with the ticket id as association handle.
* This will force the consumer to ask a verification, which will validate the service ticket.
*
* @param ticketId the service ticket to provide to the service.
* @param webApplicationService the service requesting an openid response
* @return the generated authentication answer
*/
@Override
public Response build(final WebApplicationService webApplicationService, final String ticketId) {
final OpenIdService service = (OpenIdService) webApplicationService;
final ParameterList parameterList = new ParameterList(WebUtils.getHttpServletRequestFromRequestAttributes().getParameterMap());
final Map<String, String> parameters = new HashMap<>();
if (StringUtils.isBlank(ticketId)) {
parameters.put(OpenIdProtocolConstants.OPENID_MODE, OpenIdProtocolConstants.CANCEL);
return buildRedirect(service, parameters);
}
final Association association = getAssociation(serverManager, parameterList);
final boolean associated = association != null;
final boolean associationValid = isAssociationValid(association);
boolean successFullAuthentication = true;
Assertion assertion = null;
try {
if (associated && associationValid) {
assertion = centralAuthenticationService.validateServiceTicket(ticketId, service);
LOGGER.debug("Validated openid ticket [{}] for [{}]", ticketId, service);
} else if (!associated) {
LOGGER.debug("Responding to non-associated mode. Service ticket [{}] must be validated by the RP", ticketId);
} else {
LOGGER.warn("Association does not exist or is not valid");
successFullAuthentication = false;
}
} catch (final AbstractTicketException e) {
LOGGER.error("Could not validate ticket : [{}]", e.getMessage(), e);
successFullAuthentication = false;
}
final String id = determineIdentity(service, assertion);
return buildAuthenticationResponse(service, parameters, successFullAuthentication, id, parameterList);
}
/**
* Determine identity.
*
* @param service the service
* @param assertion the assertion
* @return the string
*/
protected String determineIdentity(final OpenIdService service, final Assertion assertion) {
final String id;
if (assertion != null && OpenIdProtocolConstants.OPENID_IDENTIFIERSELECT.equals(service.getIdentity())) {
id = this.openIdPrefixUrl + '/' + assertion.getPrimaryAuthentication().getPrincipal().getId();
} else {
id = service.getIdentity();
}
return id;
}
/**
* We sign directly (final 'true') because we don't add extensions
* response message can be either a DirectError or an AuthSuccess here.
* Note:
* The association handle returned in the Response is either the 'public'
* created in a previous association, or is a 'private' handle created
* specifically for the verification step when in non-association mode
*
* @param service the service
* @param parameters the parameters
* @param successFullAuthentication the success full authentication
* @param id the id
* @param parameterList the parameter list
* @return response response
*/
protected Response buildAuthenticationResponse(final OpenIdService service,
final Map<String, String> parameters,
final boolean successFullAuthentication,
final String id,
final ParameterList parameterList) {
final Message response = serverManager.authResponse(parameterList, id, id, successFullAuthentication, true);
parameters.putAll(response.getParameterMap());
LOGGER.debug("Parameters passed for the OpenID response are [{}]", parameters.keySet());
return buildRedirect(service, parameters);
}
/**
* Gets association.
*
* @param serverManager the server manager
* @param parameterList the parameter list
* @return the association
*/
protected Association getAssociation(final ServerManager serverManager, final ParameterList parameterList) {
try {
final AuthRequest authReq = AuthRequest.createAuthRequest(parameterList, serverManager.getRealmVerifier());
final Map parameterMap = authReq.getParameterMap();
if (parameterMap != null && !parameterMap.isEmpty()) {
final String assocHandle = (String) parameterMap.get(OpenIdProtocolConstants.OPENID_ASSOCHANDLE);
if (assocHandle != null) {
return serverManager.getSharedAssociations().load(assocHandle);
}
}
} catch (final MessageException e) {
LOGGER.error("Message exception : [{}]", e.getMessage(), e);
}
return null;
}
@Override
public boolean supports(final WebApplicationService service) {
return service instanceof OpenIdService;
}
/**
* Is association valid.
*
* @param association the association
* @return true/false
*/
protected boolean isAssociationValid(final Association association) {
return association != null && !association.hasExpired();
}
}