Commit e2418393 authored by Misagh Moayyed's avatar Misagh Moayyed
Browse files

Merge pull request #1169 from Unicon/response-builders

Refactor CAS service responses: use builders
parents 2f532365 4a6ebdb9
/*
* 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.authentication.principal;
import java.io.Serializable;
/**
* Represents the task of building a CAS response
* that is returned by a service.
* @author Misagh Moayyed
* @param <T> the type parameter
* @since 4.2.0
*/
public interface ResponseBuilder<T extends WebApplicationService> extends Serializable {
/**
* Build response. The implementation must produce
* a response that is based on the type passed. Given
* the protocol used, the ticket id may be passed
* as part of the response. If the response type
* is not recognized, an error must be thrown back.
*
* @param service the service
* @param ticketId the ticket id
* @return the response
*/
Response build(T service, String ticketId);
}
......@@ -54,18 +54,23 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
private boolean loggedOutAlready;
private final ResponseBuilder<WebApplicationService> responseBuilder;
/**
* Instantiates a new abstract web application service.
*
* @param id the id
* @param originalUrl the original url
* @param artifactId the artifact id
* @param responseBuilder the response builder
*/
protected AbstractWebApplicationService(final String id, final String originalUrl,
final String artifactId) {
final String artifactId, final ResponseBuilder<WebApplicationService> responseBuilder) {
this.id = id;
this.originalUrl = originalUrl;
this.artifactId = artifactId;
this.responseBuilder = responseBuilder;
}
@Override
......@@ -73,14 +78,17 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
return this.id;
}
@Override
public final String getId() {
return this.id;
}
@Override
public final String getArtifactId() {
return this.artifactId;
}
@Override
public final Map<String, Object> getAttributes() {
return EMPTY_MAP;
}
......@@ -91,6 +99,7 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
*
* @return the original url provided.
*/
@Override
public final String getOriginalUrl() {
return this.originalUrl;
}
......@@ -117,10 +126,11 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
.toHashCode();
}
protected Principal getPrincipal() {
public Principal getPrincipal() {
return this.principal;
}
@Override
public void setPrincipal(final Principal principal) {
this.principal = principal;
}
......@@ -144,6 +154,7 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
*
* @return if the service is already logged out.
*/
@Override
public boolean isLoggedOutAlready() {
return loggedOutAlready;
}
......@@ -153,7 +164,18 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
*
* @param loggedOutAlready if the service is already logged out.
*/
@Override
public final void setLoggedOutAlready(final boolean loggedOutAlready) {
this.loggedOutAlready = loggedOutAlready;
}
protected ResponseBuilder<? extends WebApplicationService> getResponseBuilder() {
return responseBuilder;
}
@Override
public Response getResponse(final String ticketId) {
return this.responseBuilder.build(this, ticketId);
}
}
/*
* 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.authentication.principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* Abstract response builder that provides wrappers for building
* post and redirect responses.
* @author Misagh Moayyed
* @since 4.2
*/
public abstract class AbstractWebApplicationServiceResponseBuilder implements ResponseBuilder<WebApplicationService> {
private static final long serialVersionUID = -4584738964007702423L;
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Build redirect.
*
* @param service the service
* @param parameters the parameters
* @return the response
*/
protected Response buildRedirect(final WebApplicationService service, final Map<String, String> parameters) {
return DefaultResponse.getRedirectResponse(service.getOriginalUrl(), parameters);
}
/**
* Build post.
*
* @param service the service
* @param parameters the parameters
* @return the response
*/
protected Response buildPost(final WebApplicationService service, final Map<String, String> parameters) {
return DefaultResponse.getPostResponse(service.getOriginalUrl(), parameters);
}
}
......@@ -18,12 +18,6 @@
*/
package org.jasig.cas.authentication.principal;
import org.jasig.cas.CasProtocolConstants;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* Represents a service which wishes to use the CAS protocol.
*
......@@ -34,7 +28,6 @@ public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicatio
private static final long serialVersionUID = 8334068957483758042L;
private final Response.ResponseType responseType;
/**
* Instantiates a new simple web application service impl.
......@@ -42,26 +35,11 @@ public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicatio
* @param id the id
* @param originalUrl the original url
* @param artifactId the artifact id
* @param responseType the response type
* @param responseBuilder the response builder
*/
protected SimpleWebApplicationServiceImpl(final String id,
final String originalUrl, final String artifactId,
final Response.ResponseType responseType) {
super(id, originalUrl, artifactId);
this.responseType = responseType;
}
@Override
public Response getResponse(final String ticketId) {
final Map<String, String> parameters = new HashMap<>();
if (StringUtils.hasText(ticketId)) {
parameters.put(CasProtocolConstants.PARAMETER_TICKET, ticketId);
}
if (Response.ResponseType.POST == this.responseType) {
return DefaultResponse.getPostResponse(getOriginalUrl(), parameters);
}
return DefaultResponse.getRedirectResponse(getOriginalUrl(), parameters);
protected SimpleWebApplicationServiceImpl(final String id, final String originalUrl, final String artifactId,
final ResponseBuilder<WebApplicationService> responseBuilder) {
super(id, originalUrl, artifactId, responseBuilder);
}
}
......@@ -55,9 +55,12 @@ public final class WebApplicationServiceFactory extends AbstractServiceFactory<W
final String id = cleanupUrl(serviceToUse);
final String artifactId = request.getParameter(CasProtocolConstants.PARAMETER_TICKET);
return new SimpleWebApplicationServiceImpl(id, serviceToUse,
artifactId, "POST".equalsIgnoreCase(method) ? Response.ResponseType.POST
: Response.ResponseType.REDIRECT);
final Response.ResponseType type = "POST".equalsIgnoreCase(method) ? Response.ResponseType.POST
: Response.ResponseType.REDIRECT;
final WebApplicationService webApplicationService = new SimpleWebApplicationServiceImpl(id, serviceToUse,
artifactId, new WebApplicationServiceResponseBuilder(type));
return webApplicationService;
}
@Override
......
/*
* 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.authentication.principal;
import org.jasig.cas.CasProtocolConstants;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* Default response builder that passes back the ticket
* id to the original url of the service based on the response type.
* @author Misagh Moayyed
* @since 4.2
*/
public class WebApplicationServiceResponseBuilder extends AbstractWebApplicationServiceResponseBuilder {
private static final long serialVersionUID = -851233878780818494L;
private final Response.ResponseType responseType;
/**
* Instantiates a new Web application service response builder.
* @param type the type
*/
public WebApplicationServiceResponseBuilder(final Response.ResponseType type) {
this.responseType = type;
}
@Override
public Response build(final WebApplicationService service, final String ticketId) {
final Map<String, String> parameters = new HashMap<>();
if (StringUtils.hasText(ticketId)) {
parameters.put(CasProtocolConstants.PARAMETER_TICKET, ticketId);
}
if (responseType.equals(Response.ResponseType.POST)) {
return buildPost(service, parameters);
}
if (responseType.equals(Response.ResponseType.REDIRECT)) {
return buildRedirect(service, parameters);
}
throw new IllegalArgumentException("Response type is valid. Only POST/REDIRECT are supported");
}
}
......@@ -18,18 +18,16 @@
*/
package org.jasig.cas.util.http;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.security.cert.X509Certificate;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.junit.Test;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test cases for {@link SimpleHttpClient}.
......@@ -71,10 +69,13 @@ public class SimpleHttpClientTests {
private SSLConnectionSocketFactory getFriendlyToAllSSLSocketFactory() throws Exception {
final TrustManager trm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(final X509Certificate[] certs, final String authType) {}
@Override
public void checkServerTrusted(final X509Certificate[] certs, final String authType) {}
};
final SSLContext sc = SSLContext.getInstance("SSL");
......
......@@ -66,6 +66,12 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-webapp-support</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
......
......@@ -19,24 +19,11 @@
package org.jasig.cas.support.openid.authentication.principal;
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.DefaultResponse;
import org.jasig.cas.authentication.principal.Response;
import org.jasig.cas.support.openid.OpenIdProtocolConstants;
import org.jasig.cas.ticket.AbstractTicketException;
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.jasig.cas.authentication.principal.ResponseBuilder;
import org.jasig.cas.authentication.principal.WebApplicationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* @author Scott Battaglia
......@@ -51,12 +38,6 @@ public final class OpenIdService extends AbstractWebApplicationService {
private final String identity;
private final String artifactId;
private final ParameterList requestParameters;
private final String openIdPrefixUrl;
/**
* Instantiates a new OpenID service.
*
......@@ -64,100 +45,15 @@ public final class OpenIdService extends AbstractWebApplicationService {
* @param originalUrl the original url
* @param artifactId the artifact id
* @param openIdIdentity the OpenID identity
* @param signature the signature
* @param parameterList the parameter list
* @param openIdPrefixUrl the prefix url for OpenID
* @param responseBuilder the response builder
*/
protected OpenIdService(final String id, final String originalUrl,
final String artifactId, final String openIdIdentity,
final String signature, final ParameterList parameterList,
final String openIdPrefixUrl) {
super(id, originalUrl, artifactId);
final String artifactId, final String openIdIdentity,
final ResponseBuilder<WebApplicationService> responseBuilder) {
super(id, originalUrl, artifactId, responseBuilder);
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 AbstractTicketException 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.
......
......@@ -19,9 +19,13 @@
package org.jasig.cas.support.openid.authentication.principal;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.principal.AbstractServiceFactory;
import org.jasig.cas.support.openid.OpenIdProtocolConstants;
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.stereotype.Component;
import org.springframework.util.StringUtils;
......@@ -45,6 +49,14 @@ public class OpenIdServiceFactory extends AbstractServiceFactory<OpenIdService>
@Value("${server.prefix}/openid")
private String openIdPrefixUrl;
@Autowired
@Qualifier("centralAuthenticationService")
private CentralAuthenticationService centralAuthenticationService;
@Autowired
@Qualifier("serverManager")
private ServerManager serverManager;
public String getOpenIdPrefixUrl() {
return openIdPrefixUrl;
}
......@@ -57,7 +69,6 @@ public class OpenIdServiceFactory extends AbstractServiceFactory<OpenIdService>
public OpenIdService createService(final HttpServletRequest request) {
final String service = request.getParameter(OpenIdProtocolConstants.OPENID_RETURNTO);
final String openIdIdentity = request.getParameter(</