Commit 34ad07dd authored by Misagh Moayyed's avatar Misagh Moayyed
Browse files

Refactored service responses into individual components

parent 76e8b032
/*
* 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;
/**
* Represents the task of building a CAS response
* that is returned by a service.
* @param <T> the type parameter
* @author Misagh Moayyed
* @since 4.2.0
*/
public interface ResponseBuilder<T extends WebApplicationService> {
/**
* 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);
}
/*
* 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 AbstractServiceResponseBuilder implements ResponseBuilder<WebApplicationService> {
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);
}
}
......@@ -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);
}
}
......@@ -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 AbstractServiceResponseBuilder {
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");
}
}
......@@ -20,27 +20,9 @@ package org.jasig.cas.support.saml.authentication.principal;
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.services.RegisteredService;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.support.saml.SamlProtocolConstants;
import org.jasig.cas.authentication.principal.ResponseBuilder;
import org.jasig.cas.authentication.principal.WebApplicationService;
import org.jasig.cas.support.saml.util.GoogleSaml20ObjectBuilder;
import org.jasig.cas.util.ISOStandardDateFormat;
import org.joda.time.DateTime;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.AuthnContext;
import org.opensaml.saml.saml2.core.AuthnStatement;
import org.opensaml.saml.saml2.core.Conditions;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.StatusCode;
import org.opensaml.saml.saml2.core.Subject;
import java.io.StringWriter;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
/**
* Implementation of a Service that supports Google Accounts (eventually a more
* generic SAML2 support will come).
......@@ -56,27 +38,19 @@ public class GoogleAccountsService extends AbstractWebApplicationService {
private final String relayState;
private final PublicKey publicKey;
private final PrivateKey privateKey;
private final String requestId;
private final ServicesManager servicesManager;
/**
* Instantiates a new google accounts service.
*
* @param id the id
* @param relayState the relay state
* @param requestId the request id
* @param privateKey the private key
* @param publicKey the public key
* @param servicesManager the services manager
* @param responseBuilder the response builder
*/
protected GoogleAccountsService(final String id, final String relayState, final String requestId,
final PrivateKey privateKey, final PublicKey publicKey, final ServicesManager servicesManager) {
this(id, id, null, relayState, requestId, privateKey, publicKey, servicesManager);
final ResponseBuilder<WebApplicationService> responseBuilder) {
this(id, id, null, relayState, requestId, responseBuilder);
}
/**
......@@ -87,35 +61,15 @@ public class GoogleAccountsService extends AbstractWebApplicationService {
* @param artifactId the artifact id
* @param relayState the relay state
* @param requestId the request id
* @param privateKey the private key
* @param publicKey the public key
* @param servicesManager the services manager
* @param responseBuilder the response builder
*/
protected GoogleAccountsService(final String id, final String originalUrl,
final String artifactId, final String relayState, final String requestId,
final PrivateKey privateKey, final PublicKey publicKey,
final ServicesManager servicesManager) {
super(id, originalUrl, artifactId);
final String artifactId, final String relayState,
final String requestId,
final ResponseBuilder<WebApplicationService> responseBuilder) {
super(id, originalUrl, artifactId, responseBuilder);
this.relayState = relayState;
this.privateKey = privateKey;
this.publicKey = publicKey;
this.requestId = requestId;
this.servicesManager = servicesManager;
}
@Override
public Response getResponse(final String ticketId) {
final Map<String, String> parameters = new HashMap<>();
final String samlResponse = constructSamlResponse();
final String signedResponse = BUILDER.signSamlResponse(samlResponse,
this.privateKey, this.publicKey);
parameters.put(SamlProtocolConstants.PARAMETER_SAML_RESPONSE, signedResponse);
parameters.put(SamlProtocolConstants.PARAMETER_SAML_RELAY_STATE, this.relayState);
return DefaultResponse.getPostResponse(getOriginalUrl(), parameters);
}
/**
......@@ -128,45 +82,11 @@ public class GoogleAccountsService extends AbstractWebApplicationService {
return true;
}
/**
* Construct SAML response.
* <a href="http://bit.ly/1uI8Ggu">See this reference for more info.</a>
* @return the SAML response
*/
private String constructSamlResponse() {
final DateTime currentDateTime = DateTime.parse(new ISOStandardDateFormat().getCurrentDateAndTime());
final DateTime notBeforeIssueInstant = DateTime.parse("2003-04-17T00:46:02Z");
final RegisteredService svc = this.servicesManager.findServiceBy(this);
final String userId = svc.getUsernameAttributeProvider().resolveUsername(getPrincipal(), this);
final org.opensaml.saml.saml2.core.Response response = BUILDER.newResponse(
BUILDER.generateSecureRandomId(),
currentDateTime,
getId(), this);
response.setStatus(BUILDER.newStatus(StatusCode.SUCCESS, null));
final AuthnStatement authnStatement = BUILDER.newAuthnStatement(
AuthnContext.PASSWORD_AUTHN_CTX, currentDateTime);
final Assertion assertion = BUILDER.newAssertion(authnStatement,
"https://www.opensaml.org/IDP",
notBeforeIssueInstant, BUILDER.generateSecureRandomId());
final Conditions conditions = BUILDER.newConditions(notBeforeIssueInstant,
currentDateTime, getId());
assertion.setConditions(conditions);
final Subject subject = BUILDER.newSubject(NameID.EMAIL, userId,
getId(), currentDateTime, this.requestId);
assertion.setSubject(subject);
response.getAssertions().add(assertion);
final StringWriter writer = new StringWriter();
BUILDER.marshalSamlXmlObject(response, writer);
public String getRelayState() {
return relayState;
}
final String result = writer.toString();
logger.debug("Generated Google SAML response: {}", result);
return result;
public String getRequestId() {
return requestId;
}
}
......@@ -23,7 +23,6 @@ import org.apache.commons.lang3.NotImplementedException;
import org.jasig.cas.authentication.principal.AbstractServiceFactory;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.support.saml.SamlProtocolConstants;
import org.jasig.cas.support.saml.util.AbstractSaml20ObjectBuilder;
import org.jasig.cas.support.saml.util.GoogleSaml20ObjectBuilder;
import org.jdom.Document;
import org.jdom.Element;
......@@ -86,7 +85,7 @@ public class GoogleAccountsServiceFactory extends AbstractServiceFactory<GoogleA
return null;
}
final Document document = AbstractSaml20ObjectBuilder.constructDocumentFromXml(xmlRequest);
final Document document = BUILDER.constructDocumentFromXml(xmlRequest);
if (document == null) {
return null;
......@@ -96,8 +95,10 @@ public class GoogleAccountsServiceFactory extends AbstractServiceFactory<GoogleA
final String assertionConsumerServiceUrl = root.getAttributeValue("AssertionConsumerServiceURL");
final String requestId = root.getAttributeValue("ID");
return new GoogleAccountsService(assertionConsumerServiceUrl,
relayState, requestId, privateKey, publicKey, servicesManager);
final GoogleAccountsServiceResponseBuilder builder =
new GoogleAccountsServiceResponseBuilder(this.privateKey, this.publicKey,
BUILDER, this.servicesManager);
return new GoogleAccountsService(assertionConsumerServiceUrl, relayState, requestId, builder);
}
@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.support.saml.authentication.principal;
import org.jasig.cas.authentication.principal.AbstractServiceResponseBuilder;
import org.jasig.cas.authentication.principal.Response;
import org.jasig.cas.authentication.principal.WebApplicationService;
import org.jasig.cas.services.RegisteredService;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.support.saml.SamlProtocolConstants;
import org.jasig.cas.support.saml.util.GoogleSaml20ObjectBuilder;
import org.jasig.cas.util.ISOStandardDateFormat;
import org.joda.time.DateTime;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.AuthnContext;
import org.opensaml.saml.saml2.core.AuthnStatement;
import org.opensaml.saml.saml2.core.Conditions;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.StatusCode;
import org.opensaml.saml.saml2.core.Subject;
import java.io.StringWriter;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
/**
* Builds the google accounts service response.
* @author Misagh Moayyed
* @since 4.2
*/
public class GoogleAccountsServiceResponseBuilder extends AbstractServiceResponseBuilder