Commit 4268a7c8 authored by Misagh Moayyed's avatar Misagh Moayyed
Browse files

Refactored service responses into individual components

parent 1b19d087
...@@ -22,8 +22,8 @@ package org.jasig.cas.authentication.principal; ...@@ -22,8 +22,8 @@ package org.jasig.cas.authentication.principal;
/** /**
* Represents the task of building a CAS response * Represents the task of building a CAS response
* that is returned by a service. * that is returned by a service.
* @param <T> the type parameter
* @author Misagh Moayyed * @author Misagh Moayyed
* @param <T> the type parameter
* @since 4.2.0 * @since 4.2.0
*/ */
public interface ResponseBuilder<T extends WebApplicationService> { public interface ResponseBuilder<T extends WebApplicationService> {
......
...@@ -30,7 +30,7 @@ import java.util.Map; ...@@ -30,7 +30,7 @@ import java.util.Map;
* @author Misagh Moayyed * @author Misagh Moayyed
* @since 4.2 * @since 4.2
*/ */
public abstract class AbstractServiceResponseBuilder implements ResponseBuilder<WebApplicationService> { public abstract class AbstractWebApplicationServiceResponseBuilder implements ResponseBuilder<WebApplicationService> {
protected final Logger logger = LoggerFactory.getLogger(getClass()); protected final Logger logger = LoggerFactory.getLogger(getClass());
/** /**
......
...@@ -31,7 +31,7 @@ import java.util.Map; ...@@ -31,7 +31,7 @@ import java.util.Map;
* @author Misagh Moayyed * @author Misagh Moayyed
* @since 4.2 * @since 4.2
*/ */
public class WebApplicationServiceResponseBuilder extends AbstractServiceResponseBuilder { public class WebApplicationServiceResponseBuilder extends AbstractWebApplicationServiceResponseBuilder {
private final Response.ResponseType responseType; private final Response.ResponseType responseType;
/** /**
......
...@@ -66,6 +66,12 @@ ...@@ -66,6 +66,12 @@
<type>test-jar</type> <type>test-jar</type>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-webapp-support</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<properties> <properties>
......
...@@ -19,24 +19,11 @@ ...@@ -19,24 +19,11 @@
package org.jasig.cas.support.openid.authentication.principal; package org.jasig.cas.support.openid.authentication.principal;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.principal.AbstractWebApplicationService; import org.jasig.cas.authentication.principal.AbstractWebApplicationService;
import org.jasig.cas.authentication.principal.DefaultResponse; import org.jasig.cas.authentication.principal.ResponseBuilder;
import org.jasig.cas.authentication.principal.Response; import org.jasig.cas.authentication.principal.WebApplicationService;
import org.jasig.cas.support.openid.OpenIdProtocolConstants;
import org.jasig.cas.ticket.TicketException;
import org.jasig.cas.util.ApplicationContextProvider;
import org.jasig.cas.validation.Assertion;
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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author Scott Battaglia * @author Scott Battaglia
...@@ -51,12 +38,6 @@ public final class OpenIdService extends AbstractWebApplicationService { ...@@ -51,12 +38,6 @@ public final class OpenIdService extends AbstractWebApplicationService {
private final String identity; private final String identity;
private final String artifactId;
private final ParameterList requestParameters;
private final String openIdPrefixUrl;
/** /**
* Instantiates a new OpenID service. * Instantiates a new OpenID service.
* *
...@@ -64,100 +45,15 @@ public final class OpenIdService extends AbstractWebApplicationService { ...@@ -64,100 +45,15 @@ public final class OpenIdService extends AbstractWebApplicationService {
* @param originalUrl the original url * @param originalUrl the original url
* @param artifactId the artifact id * @param artifactId the artifact id
* @param openIdIdentity the OpenID identity * @param openIdIdentity the OpenID identity
* @param signature the signature * @param responseBuilder the response builder
* @param parameterList the parameter list
* @param openIdPrefixUrl the prefix url for OpenID
*/ */
protected OpenIdService(final String id, final String originalUrl, protected OpenIdService(final String id, final String originalUrl,
final String artifactId, final String openIdIdentity, final String artifactId, final String openIdIdentity,
final String signature, final ParameterList parameterList, final ResponseBuilder<WebApplicationService> responseBuilder) {
final String openIdPrefixUrl) { super(id, originalUrl, artifactId, responseBuilder);
super(id, originalUrl, artifactId);
this.identity = openIdIdentity; this.identity = openIdIdentity;
this.artifactId = artifactId;
this.requestParameters = parameterList;
this.openIdPrefixUrl = openIdPrefixUrl;
}
/**
* 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.
* @return the generated authentication answer
*/
@Override
public Response getResponse(final String ticketId) {
final Map<String, String> parameters = new HashMap<>();
if (ticketId != null) {
final ServerManager manager = (ServerManager) ApplicationContextProvider.getApplicationContext().getBean("serverManager");
final CentralAuthenticationService cas = ApplicationContextProvider.getApplicationContext()
.getBean("centralAuthenticationService", CentralAuthenticationService.class);
boolean associated = false;
boolean associationValid = true;
try {
final AuthRequest authReq = AuthRequest.createAuthRequest(requestParameters, manager.getRealmVerifier());
final Map parameterMap = authReq.getParameterMap();
if (parameterMap != null && !parameterMap.isEmpty()) {
final String assocHandle = (String) parameterMap.get(OpenIdProtocolConstants.OPENID_ASSOCHANDLE);
if (assocHandle != null) {
final Association association = manager.getSharedAssociations().load(assocHandle);
if (association != null) {
associated = true;
if (association.hasExpired()) {
associationValid = false;
}
}
}
}
} catch (final MessageException me) {
LOGGER.error("Message exception : {}", me.getMessage(), me);
} }
boolean successFullAuthentication = true;
Assertion assertion = null;
try {
if (associated) {
if (associationValid) {
assertion = cas.validateServiceTicket(ticketId, this);
LOGGER.info("Validated openid ticket");
} else {
successFullAuthentication = false;
}
}
} catch (final TicketException te) {
LOGGER.error("Could not validate ticket : {}", te.getMessage(), te);
successFullAuthentication = false;
}
final String id;
if (assertion != null && OpenIdProtocolConstants.OPENID_IDENTIFIERSELECT.equals(this.identity)) {
id = this.openIdPrefixUrl + '/' + assertion.getPrimaryAuthentication().getPrincipal().getId();
} else {
id = this.identity;
}
// We sign directly (final 'true') because we don't add extensions
// response message can be either a DirectError or an AuthSuccess here.
// Anyway, handling is the same : send the response message
final Message response = manager.authResponse(requestParameters,
id,
id,
successFullAuthentication,
true);
parameters.putAll(response.getParameterMap());
if (!associated) {
parameters.put(OpenIdProtocolConstants.OPENID_ASSOCHANDLE, ticketId);
}
} else {
parameters.put(OpenIdProtocolConstants.OPENID_MODE, OpenIdProtocolConstants.CANCEL);
}
return DefaultResponse.getRedirectResponse(getOriginalUrl(), parameters);
}
/** /**
* Return that the service is already logged out. * Return that the service is already logged out.
......
...@@ -19,9 +19,13 @@ ...@@ -19,9 +19,13 @@
package org.jasig.cas.support.openid.authentication.principal; package org.jasig.cas.support.openid.authentication.principal;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.principal.AbstractServiceFactory; import org.jasig.cas.authentication.principal.AbstractServiceFactory;
import org.jasig.cas.support.openid.OpenIdProtocolConstants; import org.jasig.cas.support.openid.OpenIdProtocolConstants;
import org.openid4java.message.ParameterList; import org.openid4java.message.ParameterList;
import org.openid4java.server.ServerManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -45,6 +49,14 @@ public class OpenIdServiceFactory extends AbstractServiceFactory<OpenIdService> ...@@ -45,6 +49,14 @@ public class OpenIdServiceFactory extends AbstractServiceFactory<OpenIdService>
@Value("${server.prefix}/openid") @Value("${server.prefix}/openid")
private String openIdPrefixUrl; private String openIdPrefixUrl;
@Autowired
@Qualifier("centralAuthenticationService")
private CentralAuthenticationService centralAuthenticationService;
@Autowired
@Qualifier("serverManager")
private ServerManager serverManager;
public String getOpenIdPrefixUrl() { public String getOpenIdPrefixUrl() {
return openIdPrefixUrl; return openIdPrefixUrl;
} }
...@@ -57,7 +69,6 @@ public class OpenIdServiceFactory extends AbstractServiceFactory<OpenIdService> ...@@ -57,7 +69,6 @@ public class OpenIdServiceFactory extends AbstractServiceFactory<OpenIdService>
public OpenIdService createService(final HttpServletRequest request) { public OpenIdService createService(final HttpServletRequest request) {
final String service = request.getParameter(OpenIdProtocolConstants.OPENID_RETURNTO); final String service = request.getParameter(OpenIdProtocolConstants.OPENID_RETURNTO);
final String openIdIdentity = request.getParameter(OpenIdProtocolConstants.OPENID_IDENTITY); final String openIdIdentity = request.getParameter(OpenIdProtocolConstants.OPENID_IDENTITY);
final String signature = request.getParameter(OpenIdProtocolConstants.OPENID_SIG);
if (openIdIdentity == null || !StringUtils.hasText(service)) { if (openIdIdentity == null || !StringUtils.hasText(service)) {
return null; return null;
...@@ -67,12 +78,14 @@ public class OpenIdServiceFactory extends AbstractServiceFactory<OpenIdService> ...@@ -67,12 +78,14 @@ public class OpenIdServiceFactory extends AbstractServiceFactory<OpenIdService>
final String artifactId = request.getParameter(OpenIdProtocolConstants.OPENID_ASSOCHANDLE); final String artifactId = request.getParameter(OpenIdProtocolConstants.OPENID_ASSOCHANDLE);
final ParameterList paramList = new ParameterList(request.getParameterMap()); final ParameterList paramList = new ParameterList(request.getParameterMap());
return new OpenIdService(id, service, artifactId, openIdIdentity, final OpenIdServiceResponseBuilder builder = new OpenIdServiceResponseBuilder(serverManager,
signature, paramList, this.openIdPrefixUrl); centralAuthenticationService, paramList, this.openIdPrefixUrl);
return new OpenIdService(id, service, artifactId, openIdIdentity, builder);
} }
@Override @Override
public OpenIdService createService(final String id) { public OpenIdService createService(final String id) {
return new OpenIdService(id, id, null, null, null, null, this.openIdPrefixUrl); return new OpenIdService(id, id, null, this.openIdPrefixUrl, null);
} }
} }
/*
* Licensed to Apereo under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Apereo licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.support.openid.authentication.principal;
import org.apache.commons.lang3.StringUtils;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.principal.AbstractWebApplicationServiceResponseBuilder;
import org.jasig.cas.authentication.principal.Response;
import org.jasig.cas.authentication.principal.WebApplicationService;
import org.jasig.cas.support.openid.OpenIdProtocolConstants;
import org.jasig.cas.ticket.TicketException;
import org.jasig.cas.validation.Assertion;
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 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 final ServerManager serverManager;
private final CentralAuthenticationService centralAuthenticationService;
private final ParameterList parameterList;
private final String openIdPrefixUrl;
/**
* Instantiates a new Open id service response builder.
*
* @param serverManager the server manager
* @param centralAuthenticationService the central authentication service
* @param parameterList the parameter list
* @param openIdPrefixUrl the open id prefix url
*/
public OpenIdServiceResponseBuilder(final ServerManager serverManager,
final CentralAuthenticationService centralAuthenticationService,
final ParameterList parameterList,
final String openIdPrefixUrl) {
this.serverManager = serverManager;
this.centralAuthenticationService = centralAuthenticationService;
this.parameterList = parameterList;
this.openIdPrefixUrl = openIdPrefixUrl;
}
/**
* 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.
* @return the generated authentication answer
*/
@Override
public Response build(final WebApplicationService webApplicationService, final String ticketId) {
final OpenIdService service = (OpenIdService) webApplicationService;
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();
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 {
logger.warn("Association does not exist or is not valid");
successFullAuthentication = false;
}
} catch (final TicketException te) {
logger.error("Could not validate ticket : {}", te.getMessage(), te);
successFullAuthentication = false;
}
final String id = determineIdentity(service, assertion);
return buildAuthenticationResponse(ticketId, service, parameters, associated,
successFullAuthentication, id);
}
/**
* 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.
*
* @param ticketId the ticket id
* @param service the service
* @param parameters the parameters
* @param associated the associated
* @param successFullAuthentication the success full authentication
* @param id the id
* @return response
*/
protected Response buildAuthenticationResponse(final String ticketId, final OpenIdService service,
final Map<String, String> parameters,
final boolean associated, final boolean successFullAuthentication,
final String id) {
final Message response = serverManager.authResponse(this.parameterList, id, id,
successFullAuthentication, true);
parameters.putAll(response.getParameterMap());
if (!associated) {
parameters.put(OpenIdProtocolConstants.OPENID_ASSOCHANDLE, ticketId);
}
return buildRedirect(service, parameters);
}
/**
* Gets association.
*
* @return the association
*/
protected Association getAssociation() {
try {
final AuthRequest authReq = AuthRequest.createAuthRequest(this.parameterList,
this.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 me) {
logger.error("Message exception : {}", me.getMessage(), me);
}
return null;
}
/**
* Is association valid.
*
* @param association the association
* @return the boolean
*/
protected boolean isAssociationValid(final Association association) {
return association != null && !association.hasExpired();
}
}
/*
* Licensed to Apereo under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Apereo licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.support.openid;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.support.openid.authentication.principal.OpenIdServiceFactory;
import org.junit.runner.RunWith;
import org.openid4java.server.ServerAssociationStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Bootstrap context for openid tests.
* @author Misagh Moayyed
* @since 4.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/META-INF/spring/openid-config.xml"})
public class AbstractOpenIdTests {
protected final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
@Qualifier("openIdServiceFactory")
protected OpenIdServiceFactory openIdServiceFactory;
@Autowired
@Qualifier("centralAuthenticationService")
protected CentralAuthenticationService centralAuthenticationService;
@Autowired
@Qualifier("serverAssociations")
protected ServerAssociationStore sharedAssociations;
}