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 ...@@ -54,18 +54,23 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
private boolean loggedOutAlready; private boolean loggedOutAlready;
private final ResponseBuilder<WebApplicationService> responseBuilder;
/** /**
* Instantiates a new abstract web application service. * Instantiates a new abstract web application service.
* *
* @param id the id * @param id the id
* @param originalUrl the original url * @param originalUrl the original url
* @param artifactId the artifact id * @param artifactId the artifact id
* @param responseBuilder the response builder
*/ */
protected AbstractWebApplicationService(final String id, final String originalUrl, protected AbstractWebApplicationService(final String id, final String originalUrl,
final String artifactId) { final String artifactId, final ResponseBuilder<WebApplicationService> responseBuilder) {
this.id = id; this.id = id;
this.originalUrl = originalUrl; this.originalUrl = originalUrl;
this.artifactId = artifactId; this.artifactId = artifactId;
this.responseBuilder = responseBuilder;
} }
@Override @Override
...@@ -73,14 +78,17 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi ...@@ -73,14 +78,17 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
return this.id; return this.id;
} }
@Override
public final String getId() { public final String getId() {
return this.id; return this.id;
} }
@Override
public final String getArtifactId() { public final String getArtifactId() {
return this.artifactId; return this.artifactId;
} }
@Override
public final Map<String, Object> getAttributes() { public final Map<String, Object> getAttributes() {
return EMPTY_MAP; return EMPTY_MAP;
} }
...@@ -91,6 +99,7 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi ...@@ -91,6 +99,7 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
* *
* @return the original url provided. * @return the original url provided.
*/ */
@Override
public final String getOriginalUrl() { public final String getOriginalUrl() {
return this.originalUrl; return this.originalUrl;
} }
...@@ -117,10 +126,11 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi ...@@ -117,10 +126,11 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
.toHashCode(); .toHashCode();
} }
protected Principal getPrincipal() { public Principal getPrincipal() {
return this.principal; return this.principal;
} }
@Override
public void setPrincipal(final Principal principal) { public void setPrincipal(final Principal principal) {
this.principal = principal; this.principal = principal;
} }
...@@ -144,6 +154,7 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi ...@@ -144,6 +154,7 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
* *
* @return if the service is already logged out. * @return if the service is already logged out.
*/ */
@Override
public boolean isLoggedOutAlready() { public boolean isLoggedOutAlready() {
return loggedOutAlready; return loggedOutAlready;
} }
...@@ -153,7 +164,18 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi ...@@ -153,7 +164,18 @@ public abstract class AbstractWebApplicationService implements SingleLogoutServi
* *
* @param loggedOutAlready if the service is already logged out. * @param loggedOutAlready if the service is already logged out.
*/ */
@Override
public final void setLoggedOutAlready(final boolean loggedOutAlready) { public final void setLoggedOutAlready(final boolean loggedOutAlready) {
this.loggedOutAlready = 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 @@ ...@@ -18,12 +18,6 @@
*/ */
package org.jasig.cas.authentication.principal; 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. * Represents a service which wishes to use the CAS protocol.
* *
...@@ -34,7 +28,6 @@ public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicatio ...@@ -34,7 +28,6 @@ public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicatio
private static final long serialVersionUID = 8334068957483758042L; private static final long serialVersionUID = 8334068957483758042L;
private final Response.ResponseType responseType;
/** /**
* Instantiates a new simple web application service impl. * Instantiates a new simple web application service impl.
...@@ -42,26 +35,11 @@ public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicatio ...@@ -42,26 +35,11 @@ public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicatio
* @param id the id * @param id the id
* @param originalUrl the original url * @param originalUrl the original url
* @param artifactId the artifact id * @param artifactId the artifact id
* @param responseType the response type * @param responseBuilder the response builder
*/ */
protected SimpleWebApplicationServiceImpl(final String id, protected SimpleWebApplicationServiceImpl(final String id, final String originalUrl, final String artifactId,
final String originalUrl, final String artifactId, final ResponseBuilder<WebApplicationService> responseBuilder) {
final Response.ResponseType responseType) { super(id, originalUrl, artifactId, responseBuilder);
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);
} }
} }
...@@ -55,9 +55,12 @@ public final class WebApplicationServiceFactory extends AbstractServiceFactory<W ...@@ -55,9 +55,12 @@ public final class WebApplicationServiceFactory extends AbstractServiceFactory<W
final String id = cleanupUrl(serviceToUse); final String id = cleanupUrl(serviceToUse);
final String artifactId = request.getParameter(CasProtocolConstants.PARAMETER_TICKET); final String artifactId = request.getParameter(CasProtocolConstants.PARAMETER_TICKET);
return new SimpleWebApplicationServiceImpl(id, serviceToUse, final Response.ResponseType type = "POST".equalsIgnoreCase(method) ? Response.ResponseType.POST
artifactId, "POST".equalsIgnoreCase(method) ? Response.ResponseType.POST : Response.ResponseType.REDIRECT;
: Response.ResponseType.REDIRECT);
final WebApplicationService webApplicationService = new SimpleWebApplicationServiceImpl(id, serviceToUse,
artifactId, new WebApplicationServiceResponseBuilder(type));
return webApplicationService;
} }
@Override @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; ...@@ -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.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.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.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 * Implementation of a Service that supports Google Accounts (eventually a more
* generic SAML2 support will come). * generic SAML2 support will come).
...@@ -56,27 +38,19 @@ public class GoogleAccountsService extends AbstractWebApplicationService { ...@@ -56,27 +38,19 @@ public class GoogleAccountsService extends AbstractWebApplicationService {
private final String relayState; private final String relayState;
private final PublicKey publicKey;
private final PrivateKey privateKey;
private final String requestId; private final String requestId;
private final ServicesManager servicesManager;
/** /**
* Instantiates a new google accounts service. * Instantiates a new google accounts service.
* *
* @param id the id * @param id the id
* @param relayState the relay state * @param relayState the relay state
* @param requestId the request id * @param requestId the request id
* @param privateKey the private key * @param responseBuilder the response builder
* @param publicKey the public key
* @param servicesManager the services manager
*/ */
protected GoogleAccountsService(final String id, final String relayState, final String requestId, protected GoogleAccountsService(final String id, final String relayState, final String requestId,
final PrivateKey privateKey, final PublicKey publicKey, final ServicesManager servicesManager) { final ResponseBuilder<WebApplicationService> responseBuilder) {
this(id, id, null, relayState, requestId, privateKey, publicKey, servicesManager); this(id, id, null, relayState, requestId, responseBuilder);
} }
/** /**
...@@ -87,35 +61,15 @@ public class GoogleAccountsService extends AbstractWebApplicationService { ...@@ -87,35 +61,15 @@ public class GoogleAccountsService extends AbstractWebApplicationService {
* @param artifactId the artifact id * @param artifactId the artifact id
* @param relayState the relay state * @param relayState the relay state
* @param requestId the request id * @param requestId the request id
* @param privateKey the private key * @param responseBuilder the response builder
* @param publicKey the public key
* @param servicesManager the services manager
*/ */
protected GoogleAccountsService(final String id, final String originalUrl, protected GoogleAccountsService(final String id, final String originalUrl,
final String artifactId, final String relayState, final String requestId, final String artifactId, final String relayState,
final PrivateKey privateKey, final PublicKey publicKey, final String requestId,
final ServicesManager servicesManager) { final ResponseBuilder<WebApplicationService> responseBuilder) {
super(id, originalUrl, artifactId); super(id, originalUrl, artifactId, responseBuilder);
this.relayState = relayState; this.relayState = relayState;
this.privateKey = privateKey;
this.publicKey = publicKey;
this.requestId = requestId; 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 { ...@@ -128,45 +82,11 @@ public class GoogleAccountsService extends AbstractWebApplicationService {
return true; return true;
} }
/** public String getRelayState() {
* Construct SAML response. return relayState;
* <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();