Author: anil.saldhana(a)jboss.com
Date: 2009-10-22 00:12:47 -0400 (Thu, 22 Oct 2009)
New Revision: 867
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/jbid-handlers.xml
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/jboss-idfed.xml
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/roles.properties
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/jbid-handlers.xml
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/jboss-idfed.xml
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/roles.properties
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/jbid-handlers.xml
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/jboss-idfed.xml
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/roles.properties
Modified:
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/idp/IDPWebBrowserSSOValve.java
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/BaseFormAuthenticator.java
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPRedirectFormAuthenticator.java
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPRedirectSignatureFormAuthenticator.java
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPUtil.java
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaContext.java
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaRequest.java
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaResponse.java
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaSession.java
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/workflow/SAML2RedirectWorkflowUnitTestCase.java
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/constants/GeneralConstants.java
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/filters/SPFilter.java
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/handlers/saml2/SAML2AuthenticationHandler.java
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/servlets/IDPServlet.java
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/util/IDPWebRequestUtil.java
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/util/RedirectBindingUtil.java
Log:
JBID-40: SAML2 Logout
Modified:
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/idp/IDPWebBrowserSSOValve.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/idp/IDPWebBrowserSSOValve.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/idp/IDPWebBrowserSSOValve.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -28,7 +28,10 @@
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
@@ -49,20 +52,40 @@
import org.jboss.identity.federation.core.config.KeyProviderType;
import org.jboss.identity.federation.core.exceptions.ConfigurationException;
import org.jboss.identity.federation.core.exceptions.ParsingException;
+import org.jboss.identity.federation.core.handler.config.Handlers;
import org.jboss.identity.federation.core.impl.DelegatedAttributeManager;
import org.jboss.identity.federation.core.interfaces.AttributeManager;
+import org.jboss.identity.federation.core.interfaces.ProtocolContext;
import org.jboss.identity.federation.core.interfaces.RoleGenerator;
import org.jboss.identity.federation.core.interfaces.TrustKeyConfigurationException;
import org.jboss.identity.federation.core.interfaces.TrustKeyManager;
import org.jboss.identity.federation.core.interfaces.TrustKeyProcessingException;
+import org.jboss.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
import org.jboss.identity.federation.core.saml.v2.constants.JBossSAMLURIConstants;
import
org.jboss.identity.federation.core.saml.v2.exceptions.IssueInstantMissingException;
import org.jboss.identity.federation.core.saml.v2.exceptions.IssuerNotTrustedException;
-import org.jboss.identity.federation.saml.v2.protocol.AuthnRequestType;
+import org.jboss.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
+import org.jboss.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerChain;
+import org.jboss.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerChainConfig;
+import org.jboss.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerRequest;
+import org.jboss.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerResponse;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2Handler;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerChain;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerChainConfig;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerRequest;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerResponse;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2Handler.HANDLER_TYPE;
+import org.jboss.identity.federation.core.saml.v2.util.HandlerUtil;
+import org.jboss.identity.federation.saml.v2.SAML2Object;
import org.jboss.identity.federation.saml.v2.protocol.RequestAbstractType;
+import org.jboss.identity.federation.saml.v2.protocol.StatusResponseType;
+import org.jboss.identity.federation.web.constants.GeneralConstants;
+import org.jboss.identity.federation.web.core.HTTPContext;
+import org.jboss.identity.federation.web.core.IdentityServer;
import org.jboss.identity.federation.web.util.ConfigurationUtil;
import org.jboss.identity.federation.web.util.IDPWebRequestUtil;
import org.jboss.identity.federation.web.util.RedirectBindingSignatureUtil;
+import org.jboss.identity.federation.web.util.RedirectBindingUtil;
import org.w3c.dom.Document;
/**
@@ -81,7 +104,7 @@
protected IDPType idpConfiguration = null;
- private RoleGenerator rg = new TomcatRoleGenerator();
+ private RoleGenerator roleGenerator = new TomcatRoleGenerator();
private long assertionValidity = 5000; // 5 seconds in miliseconds
@@ -96,6 +119,10 @@
private transient DelegatedAttributeManager attribManager = new
DelegatedAttributeManager();
private List<String> attributeKeys = new ArrayList<String>();
+ private transient SAML2HandlerChain chain = null;
+
+ private Context context = null;
+
//Set a list of attributes we are interested in separated by comma
public void setAttributeList(String attribList)
{
@@ -135,7 +162,7 @@
try
{
Class<?> clazz =
SecurityActions.getContextClassLoader().loadClass(rgName);
- rg = (RoleGenerator) clazz.newInstance();
+ roleGenerator = (RoleGenerator) clazz.newInstance();
}
catch (Exception e)
{
@@ -148,23 +175,33 @@
{
String referer = request.getHeader("Referer");
String relayState = request.getParameter("RelayState");
- String samlMessage = request.getParameter("SAMLRequest");
+
+ if(isNotNull(relayState))
+ relayState = RedirectBindingUtil.urlDecode(relayState);
+
+ String samlRequestMessage = request.getParameter("SAMLRequest");
+ String samlResponseMessage = request.getParameter("SAMLResponse");
+
String signature = request.getParameter("Signature");
String sigAlg = request.getParameter("SigAlg");
- boolean containsSAMLRequestMessage = samlMessage != null;
+ boolean containsSAMLRequestMessage = this.isNotNull(samlRequestMessage);
+ boolean containsSAMLResponseMessage = this.isNotNull(samlResponseMessage);
Session session = request.getSessionInternal();
- if(containsSAMLRequestMessage)
+ if(containsSAMLRequestMessage || containsSAMLResponseMessage)
{
- if(trace) log.trace("Storing the SAMLRequest and RelayState in
session");
- session.setNote("SAMLRequest", samlMessage);
- if(relayState != null && relayState.length() > 0)
+ if(trace) log.trace("Storing the SAMLRequest/SAMLResponse and RelayState in
session");
+ if(isNotNull(samlRequestMessage))
+ session.setNote("SAMLRequest", samlRequestMessage);
+ if(isNotNull(samlResponseMessage))
+ session.setNote("SAMLResponse", samlResponseMessage);
+ if(isNotNull(relayState))
session.setNote("RelayState", relayState.trim());
- if(signature != null && signature.length() > 0)
+ if(isNotNull(signature))
session.setNote("Signature", signature.trim());
- if(sigAlg != null && sigAlg.length() > 0)
+ if(isNotNull(sigAlg))
session.setNote("sigAlg", sigAlg.trim());
}
@@ -223,20 +260,30 @@
* we can retrieve the original saml message as well as
* any relay state from the SP
*/
- samlMessage = (String) session.getNote("SAMLRequest");
+ samlRequestMessage = (String) session.getNote("SAMLRequest");
+
+ samlResponseMessage = (String) session.getNote("SAMLResponse");
relayState = (String) session.getNote("RelayState");
signature = (String) session.getNote("Signature");
sigAlg = (String) session.getNote("sigAlg");
if(trace)
{
- log.trace("Retrieved saml message and relay state from session");
- log.trace("saml message=" + samlMessage + "::relay
state="+ relayState);
- log.trace("Signature=" + signature + "::sigAlg="+
sigAlg);
+ StringBuilder builder = new StringBuilder();
+ builder.append("Retrieved saml messages and relay state from
session");
+ builder.append("saml Request
message=").append(samlRequestMessage);
+ builder.append("::").append("SAMLResponseMessage=");
+ builder.append(samlResponseMessage).append(":").append("relay
state=").append(relayState);
+
+
builder.append("Signature=").append(signature).append("::sigAlg=").append(sigAlg);
+ log.trace(builder.toString());
}
- session.removeNote("SAMLRequest");
-
+ if(isNotNull(samlRequestMessage))
+ session.removeNote("SAMLRequest");
+ if(isNotNull(samlResponseMessage))
+ session.removeNote("SAMLResponse");
+
if(relayState != null && relayState.length() > 0)
session.removeNote("RelayState");
@@ -244,27 +291,101 @@
session.removeNote("Signature");
if(sigAlg != null && sigAlg.length() > 0)
session.removeNote("sigAlg");
+
+ boolean willSendRequest = false;
+
+ SAMLDocumentHolder samlDocumentHolder = null;
+ SAML2Object samlObject = null;
+
+ Document samlResponse = null;
+ String destination = null;
+
//Send valid saml response after processing the request
- if(samlMessage != null)
+ if(samlRequestMessage != null)
{
//Get the SAML Request Message
RequestAbstractType requestAbstractType = null;
- Document samlResponse = null;
- String destination = null;
+
try
{
- requestAbstractType = webRequestUtil.getSAMLRequest(samlMessage);
+ samlDocumentHolder =
webRequestUtil.getSAMLDocumentHolder(samlRequestMessage);
+ samlObject = (SAML2Object) samlDocumentHolder.getSamlObject();
+
+
boolean isPost = webRequestUtil.hasSAMLRequestInPostProfile();
boolean isValid = validate(request.getRemoteAddr(),
request.getQueryString(),
- new SessionHolder(samlMessage, signature, sigAlg), isPost);
+ new SessionHolder(samlRequestMessage, signature, sigAlg),
isPost);
+
if(!isValid)
throw new GeneralSecurityException("Validation check
failed");
+
+ String issuer = null;
+ IssuerInfoHolder idpIssuer = new IssuerInfoHolder(this.identityURL);
+ ProtocolContext protocolContext = new HTTPContext(request,response,
context.getServletContext());
+ //Create the request/response
+ SAML2HandlerRequest saml2HandlerRequest =
+ new DefaultSAML2HandlerRequest(protocolContext,
+ idpIssuer.getIssuer(), samlDocumentHolder,
+ HANDLER_TYPE.IDP);
+ saml2HandlerRequest.setRelayState(relayState);
+ //Set the options on the handler request
+ Map<String, Object> requestOptions = new HashMap<String,
Object>();
+ requestOptions.put(GeneralConstants.ROLE_GENERATOR, roleGenerator);
+ requestOptions.put(GeneralConstants.ASSERTIONS_VALIDITY,
this.assertionValidity);
+ requestOptions.put(GeneralConstants.CONFIGURATION,
this.idpConfiguration);
+
+ Map<String,Object> attribs =
this.attribManager.getAttributes(userPrincipal, attributeKeys);
+ requestOptions.put(GeneralConstants.ATTRIBUTES, attribs);
+
+ saml2HandlerRequest.setOptions(requestOptions);
+
+ List<String> roles = roleGenerator.generateRoles(userPrincipal);
+ session.getSession().setAttribute(GeneralConstants.ROLES_ID, roles);
+
+ SAML2HandlerResponse saml2HandlerResponse = new
DefaultSAML2HandlerResponse();
+
+ Set<SAML2Handler> handlers = chain.handlers();
+
+ if(samlObject instanceof RequestAbstractType)
+ {
+ requestAbstractType = (RequestAbstractType) samlObject;
+ issuer = requestAbstractType.getIssuer().getValue();
+ webRequestUtil.isTrusted(issuer);
+
+ if(handlers != null)
+ {
+ for(SAML2Handler handler: handlers)
+ {
+ handler.handleRequestType(saml2HandlerRequest,
saml2HandlerResponse);
+ willSendRequest = saml2HandlerResponse.getSendRequest();
+ }
+ }
+ }
+ else
+ throw new RuntimeException("Unknown type:" +
samlObject.getClass().getName());
+
+ samlResponse = saml2HandlerResponse.getResultingDocument();
+ relayState = saml2HandlerResponse.getRelayState();
+
+ destination = saml2HandlerResponse.getDestination();
+
+
+
+
+ /*requestAbstractType =
webRequestUtil.getSAMLRequest(samlRequestMessage);
+ boolean isPost = webRequestUtil.hasSAMLRequestInPostProfile();
+ boolean isValid = validate(request.getRemoteAddr(),
+ request.getQueryString(),
+ new SessionHolder(samlRequestMessage, signature, sigAlg),
isPost);
+ if(!isValid)
+ throw new GeneralSecurityException("Validation check
failed");
+
webRequestUtil.isTrusted(requestAbstractType.getIssuer().getValue());
- List<String> roles = rg.generateRoles(userPrincipal);
+ List<String> roles = roleGenerator.generateRoles(userPrincipal);
log.trace("Roles have been determined:Creating response");
@@ -274,7 +395,7 @@
samlResponse =
webRequestUtil.getResponse(destination,
userPrincipal, roles,
- this.identityURL, this.assertionValidity,
this.signOutgoingMessages);
+ this.identityURL, this.assertionValidity,
this.signOutgoingMessages); */
}
catch (IssuerNotTrustedException e)
{
@@ -339,9 +460,10 @@
if(this.signOutgoingMessages)
webRequestUtil.send(samlResponse, destination,relayState,
response, true,
- this.keyManager.getSigningKey(), false);
+ this.keyManager.getSigningKey(), willSendRequest);
else
- webRequestUtil.send(samlResponse, destination, relayState,
response, false,null, false);
+ webRequestUtil.send(samlResponse, destination, relayState,
response, false,null,
+ willSendRequest);
}
catch (ParsingException e)
{
@@ -354,9 +476,142 @@
}
return;
}
+ else if(isNotNull(samlResponseMessage))
+ {
+ StatusResponseType statusResponseType = null;
+ try
+ {
+ samlDocumentHolder =
webRequestUtil.getSAMLDocumentHolder(samlResponseMessage);
+ samlObject = (SAML2Object) samlDocumentHolder.getSamlObject();
+
+ boolean isPost = webRequestUtil.hasSAMLRequestInPostProfile();
+ boolean isValid = validate(request.getRemoteAddr(),
+ request.getQueryString(),
+ new SessionHolder(samlResponseMessage, signature, sigAlg), isPost);
+
+ if(!isValid)
+ throw new GeneralSecurityException("Validation check
failed");
+
+ String issuer = null;
+ IssuerInfoHolder idpIssuer = new IssuerInfoHolder(this.identityURL);
+ ProtocolContext protocolContext = new HTTPContext(request,response,
context.getServletContext());
+ //Create the request/response
+ SAML2HandlerRequest saml2HandlerRequest =
+ new DefaultSAML2HandlerRequest(protocolContext,
+ idpIssuer.getIssuer(), samlDocumentHolder,
+ HANDLER_TYPE.IDP);
+ saml2HandlerRequest.setRelayState(relayState);
+
+ SAML2HandlerResponse saml2HandlerResponse = new
DefaultSAML2HandlerResponse();
+
+ Set<SAML2Handler> handlers = chain.handlers();
+
+ if(samlObject instanceof StatusResponseType)
+ {
+ statusResponseType = (StatusResponseType) samlObject;
+ issuer = statusResponseType.getIssuer().getValue();
+ webRequestUtil.isTrusted(issuer);
+
+ if(handlers != null)
+ {
+ for(SAML2Handler handler: handlers)
+ {
+ handler.reset();
+ handler.handleStatusResponseType(saml2HandlerRequest,
saml2HandlerResponse);
+ willSendRequest = saml2HandlerResponse.getSendRequest();
+ }
+ }
+ }
+ else
+ throw new RuntimeException("Unknown type:" +
samlObject.getClass().getName());
+
+ samlResponse = saml2HandlerResponse.getResultingDocument();
+ relayState = saml2HandlerResponse.getRelayState();
+
+ destination = saml2HandlerResponse.getDestination();
+ }
+ catch (IssuerNotTrustedException e)
+ {
+ if(trace) log.trace("Exception in processing request:",e);
+
+ samlResponse =
+ webRequestUtil.getErrorResponse(referer,
+ JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get(),
+ this.identityURL, this.signOutgoingMessages);
+ }
+ catch (ParsingException e)
+ {
+ if(trace) log.trace("Exception in processing request:",e);
+
+ samlResponse =
+ webRequestUtil.getErrorResponse(referer,
+ JBossSAMLURIConstants.STATUS_AUTHNFAILED.get(),
+ this.identityURL, this.signOutgoingMessages);
+ }
+ catch (ConfigurationException e)
+ {
+ if(trace) log.trace("Exception in processing request:",e);
+
+ samlResponse =
+ webRequestUtil.getErrorResponse(referer,
+ JBossSAMLURIConstants.STATUS_AUTHNFAILED.get(),
+ this.identityURL, this.signOutgoingMessages);
+ }
+ catch (IssueInstantMissingException e)
+ {
+ if(trace) log.trace("Exception in processing request:",e);
+
+ samlResponse =
+ webRequestUtil.getErrorResponse(referer,
+ JBossSAMLURIConstants.STATUS_AUTHNFAILED.get(),
+ this.identityURL, this.signOutgoingMessages);
+ }
+ catch(GeneralSecurityException e)
+ {
+ if(trace) log.trace("Exception in processing request:", e);
+
+ samlResponse =
+ webRequestUtil.getErrorResponse(referer,
+ JBossSAMLURIConstants.STATUS_AUTHNFAILED.get(),
+ this.identityURL, this.signOutgoingMessages);
+ }
+ catch(Exception e)
+ {
+ if(trace) log.trace("Exception in processing request:",e);
+
+ samlResponse =
+ webRequestUtil.getErrorResponse(referer,
+ JBossSAMLURIConstants.STATUS_AUTHNFAILED.get(),
+ this.identityURL, this.signOutgoingMessages);
+ }
+ finally
+ {
+ try
+ {
+ if(webRequestUtil.hasSAMLRequestInPostProfile())
+ recycle(response);
+
+ if(this.signOutgoingMessages)
+ webRequestUtil.send(samlResponse, destination,relayState, response,
true,
+ this.keyManager.getSigningKey(), willSendRequest);
+ else
+ webRequestUtil.send(samlResponse, destination, relayState, response,
false,null,
+ willSendRequest);
+ }
+ catch (ParsingException e)
+ {
+ if(trace) log.trace("Parsing exception:", e);
+ }
+ catch (GeneralSecurityException e)
+ {
+ if(trace) log.trace("Security Exception:",e);
+ }
+ }
+ return;
+ }
else
{
- log.error("No SAML Request Message");
+ log.error("No SAML Request or Response Message");
if(trace) log.trace("Referer="+referer);
try
@@ -499,6 +754,8 @@
*/
public void start() throws LifecycleException
{
+ Handlers handlers = null;
+
// Validate and update our current component state
if (started)
throw new LifecycleException
@@ -506,8 +763,11 @@
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
+ //Get the chain from config
+ chain = new DefaultSAML2HandlerChain();
+
String configFile = "/WEB-INF/jboss-idfed.xml";
- Context context = (Context) getContainer();
+ context = (Context) getContainer();
InputStream is = context.getServletContext().getResourceAsStream(configFile);
if(is == null)
throw new RuntimeException(configFile + " missing");
@@ -525,6 +785,21 @@
AttributeManager delegate = (AttributeManager)
tcl.loadClass(attributeManager).newInstance();
this.attribManager.setDelegate(delegate);
}
+ //Get the handlers
+ handlers =
ConfigurationUtil.getHandlers(context.getServletContext().getResourceAsStream("/WEB-INF/jbid-handlers.xml"));
+ chain.addAll(HandlerUtil.getHandlers(handlers));
+
+ Map<String, Object> chainConfigOptions = new HashMap<String,
Object>();
+ chainConfigOptions.put(GeneralConstants.ROLE_GENERATOR, roleGenerator);
+ chainConfigOptions.put(GeneralConstants.CONFIGURATION, idpConfiguration);
+
+ SAML2HandlerChainConfig handlerChainConfig = new
DefaultSAML2HandlerChainConfig(chainConfigOptions);
+ Set<SAML2Handler> samlHandlers = chain.handlers();
+
+ for(SAML2Handler handler: samlHandlers)
+ {
+ handler.initChainConfig(handlerChainConfig);
+ }
}
catch (Exception e)
{
@@ -564,6 +839,16 @@
"facsimileTelephoneNumber"};
this.attributeKeys.addAll(Arrays.asList(ak));
+
+ //The Identity Server on the servlet context gets set
+ //in the implementation of IdentityServer
+ //Create an Identity Server and set it on the context
+ IdentityServer identityServer = (IdentityServer)
context.getServletContext().getAttribute(GeneralConstants.IDENTITY_SERVER);
+ if(identityServer == null)
+ {
+ identityServer = new IdentityServer();
+ context.getServletContext().setAttribute(GeneralConstants.IDENTITY_SERVER,
identityServer);
+ }
}
@@ -610,4 +895,9 @@
*/
response.recycle();
}
+
+ private boolean isNotNull(String str)
+ {
+ return str != null && !"".equals(str);
+ }
}
\ No newline at end of file
Modified:
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/BaseFormAuthenticator.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/BaseFormAuthenticator.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/BaseFormAuthenticator.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -24,12 +24,25 @@
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletContext;
+
import org.apache.catalina.LifecycleException;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.log4j.Logger;
import org.jboss.identity.federation.core.config.SPType;
+import org.jboss.identity.federation.core.handler.config.Handlers;
+import org.jboss.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerChain;
+import org.jboss.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerChainConfig;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2Handler;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerChain;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerChainConfig;
+import org.jboss.identity.federation.core.saml.v2.util.HandlerUtil;
+import org.jboss.identity.federation.web.constants.GeneralConstants;
import org.jboss.identity.federation.web.util.ConfigurationUtil;
/**
@@ -48,6 +61,8 @@
protected String identityURL = null;
protected String configFile = "/WEB-INF/jboss-idfed.xml";
+
+ protected transient SAML2HandlerChain chain = null;
public BaseFormAuthenticator()
{
@@ -80,10 +95,26 @@
public void start() throws LifecycleException
{
super.start();
+ processStart();
+ }
+
+ //Mock test purpose
+ public void testStart() throws LifecycleException
+ {
+ processStart();
+ }
+
+ private void processStart() throws LifecycleException
+ {
+ Handlers handlers = null;
- InputStream is = context.getServletContext().getResourceAsStream(configFile);
+ ServletContext servletContext = context.getServletContext();
+ InputStream is = servletContext.getResourceAsStream(configFile);
if(is == null)
throw new RuntimeException(configFile + " missing");
+
+ //Get the chain from config
+ chain = new DefaultSAML2HandlerChain();
try
{
spConfiguration = ConfigurationUtil.getSPConfiguration(is);
@@ -95,24 +126,30 @@
{
throw new RuntimeException(e);
}
- }
-
- //Mock test purpose
- public void testStart() throws LifecycleException
- {
- InputStream is = context.getServletContext().getResourceAsStream(configFile);
- if(is == null)
- throw new RuntimeException(configFile + " missing");
+
+ //Get the chain from config
+ chain = new DefaultSAML2HandlerChain();
try
{
- spConfiguration = ConfigurationUtil.getSPConfiguration(is);
- this.identityURL = spConfiguration.getIdentityURL();
- this.serviceURL = spConfiguration.getServiceURL();
- if(trace) log.trace("Identity Provider URL=" + this.identityURL);
+ //Get the handlers
+ handlers =
ConfigurationUtil.getHandlers(servletContext.getResourceAsStream("/WEB-INF/jbid-handlers.xml"));
+ chain.addAll(HandlerUtil.getHandlers(handlers));
+
+ Map<String, Object> chainConfigOptions = new HashMap<String,
Object>();
+ chainConfigOptions.put(GeneralConstants.CONFIGURATION, spConfiguration);
+ chainConfigOptions.put(GeneralConstants.ROLE_VALIDATOR_IGNORE,
"false"); //No validator as tomcat realm does validn
+
+ SAML2HandlerChainConfig handlerChainConfig = new
DefaultSAML2HandlerChainConfig(chainConfigOptions);
+ Set<SAML2Handler> samlHandlers = chain.handlers();
+
+ for(SAML2Handler handler: samlHandlers)
+ {
+ handler.initChainConfig(handlerChainConfig);
+ }
}
- catch (Exception e)
+ catch(Exception e)
{
- throw new RuntimeException(e);
- }
- }
+ throw new RuntimeException(e);
+ }
+ }
}
\ No newline at end of file
Modified:
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPRedirectFormAuthenticator.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPRedirectFormAuthenticator.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPRedirectFormAuthenticator.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -28,8 +28,10 @@
import java.security.Principal;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
import java.util.StringTokenizer;
+import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.xml.bind.JAXBException;
@@ -44,19 +46,35 @@
import org.jboss.identity.federation.api.saml.v2.response.SAML2Response;
import org.jboss.identity.federation.api.util.Base64;
import org.jboss.identity.federation.api.util.DeflateUtil;
-import org.jboss.identity.federation.core.config.TrustType;
import
org.jboss.identity.federation.bindings.tomcat.sp.holder.ServiceProviderSAMLContext;
-import org.jboss.identity.federation.web.util.HTTPRedirectUtil;
-import org.jboss.identity.federation.web.util.RedirectBindingUtil;
-import org.jboss.identity.federation.web.util.ServerDetector;
import org.jboss.identity.federation.bindings.util.ValveUtil;
+import org.jboss.identity.federation.core.config.TrustType;
import org.jboss.identity.federation.core.exceptions.ConfigurationException;
import org.jboss.identity.federation.core.exceptions.ParsingException;
-import org.jboss.identity.federation.core.saml.v2.exceptions.AssertionExpiredException;
+import org.jboss.identity.federation.core.exceptions.ProcessingException;
+import org.jboss.identity.federation.core.interfaces.ProtocolContext;
+import org.jboss.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
import org.jboss.identity.federation.core.saml.v2.exceptions.IssuerNotTrustedException;
+import org.jboss.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
+import org.jboss.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerRequest;
+import org.jboss.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerResponse;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2Handler;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerRequest;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerResponse;
+import org.jboss.identity.federation.core.saml.v2.interfaces.SAML2Handler.HANDLER_TYPE;
+import
org.jboss.identity.federation.core.saml.v2.interfaces.SAML2HandlerRequest.GENERATE_REQUEST_TYPE;
+import org.jboss.identity.federation.core.saml.v2.util.DocumentUtil;
+import org.jboss.identity.federation.saml.v2.SAML2Object;
import org.jboss.identity.federation.saml.v2.assertion.EncryptedElementType;
import org.jboss.identity.federation.saml.v2.protocol.AuthnRequestType;
+import org.jboss.identity.federation.saml.v2.protocol.RequestAbstractType;
import org.jboss.identity.federation.saml.v2.protocol.ResponseType;
+import org.jboss.identity.federation.web.constants.GeneralConstants;
+import org.jboss.identity.federation.web.core.HTTPContext;
+import org.jboss.identity.federation.web.util.HTTPRedirectUtil;
+import org.jboss.identity.federation.web.util.RedirectBindingUtil;
+import org.jboss.identity.federation.web.util.ServerDetector;
+import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
@@ -74,6 +92,8 @@
private boolean jbossEnv = false;
+ private String logOutPage = "/logout.jsp";
+
public SPRedirectFormAuthenticator()
{
super();
@@ -84,71 +104,354 @@
@Override
public boolean authenticate(Request request, Response response, LoginConfig
loginConfig) throws IOException
{
- Principal principal = request.getUserPrincipal();
- if (principal != null)
- {
- if(trace)
- log.trace("Already authenticated '" + principal.getName() +
"'");
- return true;
- }
+ Principal principal = request.getUserPrincipal();
+ SAML2Request saml2Request = new SAML2Request();
+
Session session = request.getSessionInternal(true);
String relayState = request.getParameter("RelayState");
-
- //Try to get the username
- try
- {
- principal = (GenericPrincipal) process(request,response);
+
+ String samlRequest = request.getParameter("SAMLRequest");
+ String samlResponse = request.getParameter("SAMLResponse");
+
+ //Eagerly look for Global LogOut
+ String gloStr = request.getParameter(GeneralConstants.GLOBAL_LOGOUT);
+ boolean logOutRequest = isNotNull(gloStr) &&
"true".equalsIgnoreCase(gloStr);
+
+
+ /* if(!logOutRequest)
+ {*/
+ /* if (principal != null)
+ {
+ if(trace)
+ log.trace("Already authenticated '" + principal.getName() +
"'");
+ return true;
+ }*/
- if(principal == null)
+ /* //Try to get the username
+ try
{
- String destination = createSAMLRequestMessage( relayState, response);
- HTTPRedirectUtil.sendRedirectForRequestor(destination, response);
+ principal = (GenericPrincipal) process(request,response);
+ if(principal == null)
+ {
+ String destination = createSAMLRequestMessage( relayState, response);
+ HTTPRedirectUtil.sendRedirectForRequestor(destination, response);
+
+ return false;
+ }
+
+ String username = principal.getName();
+ String password = ServiceProviderSAMLContext.EMPTY_PASSWORD;
+
+ //Map to JBoss specific principal
+ if(spConfiguration.getServerEnvironment().equalsIgnoreCase("JBOSS")
|| jbossEnv)
+ {
+ GenericPrincipal gp = (GenericPrincipal) principal;
+ //Push a context
+ ServiceProviderSAMLContext.push(username, Arrays.asList(gp.getRoles()));
+ principal = context.getRealm().authenticate(username, password);
+ ServiceProviderSAMLContext.clear();
+ }
+
+ session.setNote(Constants.SESS_USERNAME_NOTE, username);
+ session.setNote(Constants.SESS_PASSWORD_NOTE, password);
+ request.setUserPrincipal(principal);
+ register(request, response, principal, Constants.FORM_METHOD, username,
password);
+
+ return true;
+ }
+ catch(AssertionExpiredException aie)
+ {
+ if(trace)
+ log.trace("Assertion has expired. Issuing a new saml2 request to the
IDP");
+ try
+ {
+ String destination = createSAMLRequestMessage( relayState, response);
+ HTTPRedirectUtil.sendRedirectForRequestor(destination, response);
+ }
+ catch (Exception e)
+ {
+ if(trace) log.trace("Exception:",e);
+ }
return false;
}
-
- String username = principal.getName();
- String password = ServiceProviderSAMLContext.EMPTY_PASSWORD;
-
- //Map to JBoss specific principal
- if(spConfiguration.getServerEnvironment().equalsIgnoreCase("JBOSS") ||
jbossEnv)
+ catch(Exception e)
{
- GenericPrincipal gp = (GenericPrincipal) principal;
- //Push a context
- ServiceProviderSAMLContext.push(username, Arrays.asList(gp.getRoles()));
- principal = context.getRealm().authenticate(username, password);
- ServiceProviderSAMLContext.clear();
- }
-
- session.setNote(Constants.SESS_USERNAME_NOTE, username);
- session.setNote(Constants.SESS_PASSWORD_NOTE, password);
- request.setUserPrincipal(principal);
- register(request, response, principal, Constants.FORM_METHOD, username,
password);
-
- return true;
+ if(trace)
+ log.trace("Exception :",e);
+ }
}
- catch(AssertionExpiredException aie)
- {
- if(trace)
- log.trace("Assertion has expired. Issuing a new saml2 request to the
IDP");
- try
+ else
+ {*/
+ if(!isNotNull(samlRequest) && !isNotNull(samlResponse))
{
- String destination = createSAMLRequestMessage( relayState, response);
- HTTPRedirectUtil.sendRedirectForRequestor(destination, response);
+ //Neither saml request nor response from IDP
+ //So this is a user request
+
+ //Ask the handler chain to generate the saml request
+ Set<SAML2Handler> handlers = chain.handlers();
+
+ IssuerInfoHolder holder = new IssuerInfoHolder(this.serviceURL);
+ ProtocolContext protocolContext = new HTTPContext(request,response,
context.getServletContext());
+ //Create the request/response
+ SAML2HandlerRequest saml2HandlerRequest =
+ new DefaultSAML2HandlerRequest(protocolContext,
+ holder.getIssuer(), null,
+ HANDLER_TYPE.SP);
+ SAML2HandlerResponse saml2HandlerResponse = new
DefaultSAML2HandlerResponse();
+
+ saml2HandlerResponse.setDestination(identityURL);
+
+ //Reset the state
+ try
+ {
+ for(SAML2Handler handler: handlers)
+ {
+ handler.reset();
+ if(saml2HandlerResponse.isInError())
+ {
+ response.sendError(saml2HandlerResponse.getErrorCode());
+ break;
+ }
+
+ if(logOutRequest)
+
saml2HandlerRequest.setTypeOfRequestToBeGenerated(GENERATE_REQUEST_TYPE.LOGOUT);
+ else
+
saml2HandlerRequest.setTypeOfRequestToBeGenerated(GENERATE_REQUEST_TYPE.AUTH);
+ handler.generateSAMLRequest(saml2HandlerRequest,
saml2HandlerResponse);
+ }
+ }
+ catch(ProcessingException pe)
+ {
+ throw new RuntimeException(pe);
+ }
+ Document samlResponseDocument = saml2HandlerResponse.getResultingDocument();
+ relayState = saml2HandlerResponse.getRelayState();
+
+ String destination = saml2HandlerResponse.getDestination();
+
+ if(destination != null &&
+ samlResponseDocument != null)
+ {
+ try
+ {
+ String samlMsg =
DocumentUtil.getDocumentAsString(samlResponseDocument);
+
+ String base64Request =
RedirectBindingUtil.deflateBase64URLEncode(samlMsg.getBytes("UTF-8"));
+ String destinationURL = destination +
+ getDestination(base64Request, relayState,
saml2HandlerResponse.getSendRequest());
+
+ HTTPRedirectUtil.sendRedirectForRequestor(destinationURL, response);
+ }
+ catch (Exception e)
+ {
+ if(trace)
+ log.trace("Exception:",e);
+ throw new IOException("Server Error");
+ }
+ return true;
+ }
}
- catch (Exception e)
+
+ //See if we got a response from IDP
+ if(isNotNull(samlResponse) )
{
- if(trace) log.trace("Exception:",e);
- }
- return false;
- }
- catch(Exception e)
- {
- if(trace)
- log.trace("Exception :",e);
- }
+ boolean isValid = false;
+ try
+ {
+ isValid = this.validate(request);
+ }
+ catch (Exception e)
+ {
+ log.error("Exception:",e);
+ throw new IOException();
+ }
+ if(!isValid)
+ throw new IOException("Validity check failed");
+ //deal with SAML response from IDP
+ InputStream base64DecodedResponse =
RedirectBindingUtil.base64DeflateDecode(samlResponse);
+
+ try
+ {
+ SAML2Response saml2Response = new SAML2Response();
+
+ SAML2Object samlObject =
saml2Response.getSAML2ObjectFromStream(base64DecodedResponse);
+ SAMLDocumentHolder documentHolder =
saml2Response.getSamlDocumentHolder();
+
+ Set<SAML2Handler> handlers = chain.handlers();
+ IssuerInfoHolder holder = new IssuerInfoHolder(this.serviceURL);
+ ProtocolContext protocolContext = new HTTPContext(request,response,
context.getServletContext());
+ //Create the request/response
+ SAML2HandlerRequest saml2HandlerRequest =
+ new DefaultSAML2HandlerRequest(protocolContext,
+ holder.getIssuer(), documentHolder,
+ HANDLER_TYPE.SP);
+
+ SAML2HandlerResponse saml2HandlerResponse = new
DefaultSAML2HandlerResponse();
+
+ //Deal with handler chains
+ for(SAML2Handler handler : handlers)
+ {
+ if(saml2HandlerResponse.isInError())
+ {
+ response.sendError(saml2HandlerResponse.getErrorCode());
+ break;
+ }
+ if(samlObject instanceof RequestAbstractType)
+ {
+ handler.handleRequestType(saml2HandlerRequest,
saml2HandlerResponse);
+ }
+ else
+ {
+ handler.handleStatusResponseType(saml2HandlerRequest,
saml2HandlerResponse);
+ }
+ }
+
+ Document samlResponseDocument =
saml2HandlerResponse.getResultingDocument();
+ relayState = saml2HandlerResponse.getRelayState();
+
+ String destination = saml2HandlerResponse.getDestination();
+
+
+ if(destination != null &&
+ samlResponseDocument != null)
+ {
+ String samlMsg =
DocumentUtil.getDocumentAsString(samlResponseDocument);
+
+ String base64Request =
RedirectBindingUtil.deflateBase64URLEncode(samlMsg.getBytes("UTF-8"));
+ String destinationURL = destination +
+ getDestination(base64Request, relayState,
saml2HandlerResponse.getSendRequest());
+
+ HTTPRedirectUtil.sendRedirectForRequestor(destinationURL, response);
+ }
+ else
+ {
+ //We got a response with the principal
+ List<String> roles = saml2HandlerResponse.getRoles();
+ if(principal == null)
+ principal = (Principal)
session.getSession().getAttribute(GeneralConstants.PRINCIPAL_ID);
+
+ String username = principal.getName();
+ String password = ServiceProviderSAMLContext.EMPTY_PASSWORD;
+
+ //Map to JBoss specific principal
+ if((new ServerDetector()).isJboss() || jbossEnv)
+ {
+ GenericPrincipal gp = (GenericPrincipal) principal;
+ //Push a context
+ ServiceProviderSAMLContext.push(username,
Arrays.asList(gp.getRoles()));
+ principal = context.getRealm().authenticate(username, password);
+ ServiceProviderSAMLContext.clear();
+ }
+ else
+ {
+ //tomcat env
+ SPUtil spUtil = new SPUtil();
+ principal = spUtil.createGenericPrincipal(request,
principal.getName(), roles);
+ }
+
+ session.setNote(Constants.SESS_USERNAME_NOTE, username);
+ session.setNote(Constants.SESS_PASSWORD_NOTE, password);
+ request.setUserPrincipal(principal);
+ register(request, response, principal, Constants.FORM_METHOD, username,
password);
+
+ return true;
+ }
+
+ //See if the session has been invalidated
+
+ boolean sessionValidity = session.isValid();
+ if(!sessionValidity)
+ {
+ //we are invalidated.
+ RequestDispatcher dispatch =
context.getServletContext().getRequestDispatcher(this.logOutPage);
+ if(dispatch == null)
+ log.error("Cannot dispatch to the logout page: no request
dispatcher:" + this.logOutPage);
+ else
+ dispatch.forward(request, response);
+ return false;
+ }
+ }
+ catch (Exception e)
+ {
+ if(trace)
+ log.trace("Server Exception:", e);
+ throw new IOException("Server Exception");
+ }
+ }
+
+
+ if(isNotNull(samlRequest))
+ {
+ //we got a logout request
+
+ //deal with SAML response from IDP
+ InputStream is = RedirectBindingUtil.base64DeflateDecode(samlRequest);
+
+ try
+ {
+ SAML2Object samlObject = saml2Request.getSAML2ObjectFromStream(is);
+ SAMLDocumentHolder documentHolder =
saml2Request.getSamlDocumentHolder();
+
+ Set<SAML2Handler> handlers = chain.handlers();
+ IssuerInfoHolder holder = new IssuerInfoHolder(this.serviceURL);
+ ProtocolContext protocolContext = new HTTPContext(request,response,
context.getServletContext());
+ //Create the request/response
+ SAML2HandlerRequest saml2HandlerRequest =
+ new DefaultSAML2HandlerRequest(protocolContext,
+ holder.getIssuer(), documentHolder,
+ HANDLER_TYPE.SP);
+
+ SAML2HandlerResponse saml2HandlerResponse = new
DefaultSAML2HandlerResponse();
+
+ //Deal with handler chains
+ for(SAML2Handler handler : handlers)
+ {
+ if(saml2HandlerResponse.isInError())
+ {
+ response.sendError(saml2HandlerResponse.getErrorCode());
+ break;
+ }
+ if(samlObject instanceof RequestAbstractType)
+ {
+ handler.handleRequestType(saml2HandlerRequest,
saml2HandlerResponse);
+ }
+ else
+ {
+ handler.handleStatusResponseType(saml2HandlerRequest,
saml2HandlerResponse);
+ }
+ }
+
+ Document samlResponseDocument =
saml2HandlerResponse.getResultingDocument();
+ relayState = saml2HandlerResponse.getRelayState();
+
+ String destination = saml2HandlerResponse.getDestination();
+
+
+ if(destination != null &&
+ samlResponseDocument != null)
+ {
+ String samlMsg =
DocumentUtil.getDocumentAsString(samlResponseDocument);
+
+ String base64Request =
RedirectBindingUtil.deflateBase64URLEncode(samlMsg.getBytes("UTF-8"));
+ String destinationURL = destination +
+ getDestination(base64Request, relayState,
saml2HandlerResponse.getSendRequest());
+
+ HTTPRedirectUtil.sendRedirectForRequestor(destinationURL,
response);
+ return true;
+ }
+ }
+ catch (Exception e)
+ {
+ if(trace)
+ log.trace("Server Exception:", e);
+ throw new IOException("Server Exception");
+ }
+ }//end else logoutrequest
+ /* }*/
+
//fallback
return super.authenticate(request, response, loginConfig);
}
@@ -169,17 +472,21 @@
saml2Request.marshall(authnRequest, baos);
String base64Request =
RedirectBindingUtil.deflateBase64URLEncode(baos.toByteArray());
- String destination = authnRequest.getDestination() + getDestination(base64Request,
relayState);
+ String destination = authnRequest.getDestination() + getDestination(base64Request,
relayState, true);
if(trace)
log.trace("Sending to destination="+destination);
return destination;
}
- protected String getDestination(String urlEncodedRequest, String
urlEncodedRelayState)
+ protected String getDestination(String urlEncodedRequest, String
urlEncodedRelayState,
+ boolean sendRequest)
{
StringBuilder sb = new StringBuilder();
- sb.append("?SAMLRequest=").append(urlEncodedRequest);
+ if(sendRequest)
+ sb.append("?SAMLRequest=").append(urlEncodedRequest);
+ else
+ sb.append("?SAMLResponse=").append(urlEncodedRequest);
if(urlEncodedRelayState != null && urlEncodedRelayState.length() > 0)
sb.append("&RelayState=").append(urlEncodedRelayState);
return sb.toString();
@@ -270,4 +577,10 @@
}
return userPrincipal;
}
+
+
+ private boolean isNotNull(String str)
+ {
+ return str != null && !"".equals(str);
+ }
}
\ No newline at end of file
Modified:
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPRedirectSignatureFormAuthenticator.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPRedirectSignatureFormAuthenticator.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPRedirectSignatureFormAuthenticator.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -140,7 +140,7 @@
}
@Override
- protected String getDestination(String urlEncodedRequest, String
urlEncodedRelayState)
+ protected String getDestination(String urlEncodedRequest, String urlEncodedRelayState,
boolean sendRequest)
{
try
{
Modified:
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPUtil.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPUtil.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/main/java/org/jboss/identity/federation/bindings/tomcat/sp/SPUtil.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -128,7 +128,7 @@
return this.createGenericPrincipal(request, userName, roles);
}
- private Principal createGenericPrincipal(Request request, String username,
List<String> roles)
+ public Principal createGenericPrincipal(Request request, String username,
List<String> roles)
{
Context ctx = request.getContext();
return new GenericPrincipal(ctx.getRealm(), username, null, roles);
Modified:
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaContext.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaContext.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaContext.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -150,8 +150,7 @@
}
public String getName()
- {
-
+ {
throw new RuntimeException("NYI");
}
Modified:
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaRequest.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaRequest.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaRequest.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -25,6 +25,8 @@
import java.util.HashMap;
import java.util.Map;
+import javax.servlet.http.HttpSession;
+
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
@@ -61,6 +63,11 @@
{
return principal;
}
+
+ public Principal getUserPrincipal()
+ {
+ return principal;
+ }
@Override
public void setUserPrincipal(Principal arg0)
@@ -131,4 +138,20 @@
{
this.session = s;
}
+
+ public HttpSession getSession(boolean b)
+ {
+ return this.session.getSession();
+ }
+
+ public HttpSession getSession()
+ {
+ return this.session.getSession();
+ }
+
+ public void clear()
+ {
+ this.params.clear();
+ this.session = null;
+ }
}
\ No newline at end of file
Modified:
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaResponse.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaResponse.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaResponse.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -22,6 +22,7 @@
package org.jboss.test.identity.federation.bindings.mock;
import java.io.IOException;
+import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
@@ -37,6 +38,8 @@
private Map<String, String> headers = new HashMap<String, String>();
private int status;
public String redirectString;
+ @SuppressWarnings("unused")
+ private Writer mywriter;
@Override
public void setCharacterEncoding(String charset)
@@ -71,6 +74,10 @@
public boolean isCommitted()
{
return false;
- }
-
+ }
+
+ public void setWriter(Writer w)
+ {
+ this.mywriter = w;
+ }
}
\ No newline at end of file
Modified:
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaSession.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaSession.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/mock/MockCatalinaSession.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -286,4 +286,9 @@
this.attribs.put(arg0, arg1);
}
+
+ public void clear()
+ {
+ this.notes.clear();
+ }
}
\ No newline at end of file
Modified:
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/workflow/SAML2RedirectWorkflowUnitTestCase.java
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/workflow/SAML2RedirectWorkflowUnitTestCase.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/java/org/jboss/test/identity/federation/bindings/workflow/SAML2RedirectWorkflowUnitTestCase.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -40,7 +40,7 @@
*/
public class SAML2RedirectWorkflowUnitTestCase extends TestCase
{
- private String profile = "saml2/logout";
+ private String profile = "saml2/redirect";
private ClassLoader tcl = Thread.currentThread().getContextClassLoader();
private String employee = "http://localhost:8080/employee/";
@@ -70,8 +70,8 @@
String redirectStr = response.redirectString;
assertNotNull("Redirect String is null?", redirectStr);
- String saml =
RedirectBindingUtil.urlDecode(redirectStr.substring(redirectStr.indexOf(SAML_REQUEST_KEY)
+
- SAML_REQUEST_KEY.length()) );
+ String saml = redirectStr.substring(redirectStr.indexOf(SAML_REQUEST_KEY) +
+ SAML_REQUEST_KEY.length());
MockCatalinaSession session = new MockCatalinaSession();
@@ -94,7 +94,7 @@
request = new MockCatalinaRequest();
request.setRemoteAddr(employee);
request.setSession(session);
- request.setParameter("SAMLRequest", saml);
+ request.setParameter("SAMLRequest",
RedirectBindingUtil.urlDecode(saml));
request.setUserPrincipal(new GenericPrincipal(realm, "anil",
"test", roles) );
request.setMethod("GET");
@@ -113,9 +113,8 @@
SAML_RESPONSE_KEY.length()));
mclSPEmp = setupTCL(profile + "/sp/employee");
- Thread.currentThread().setContextClassLoader(mclSPEmp);
+ Thread.currentThread().setContextClassLoader(mclSPEmp);
-
sp = new SPRedirectFormAuthenticator();
context = new MockCatalinaContext();
@@ -124,6 +123,7 @@
request = new MockCatalinaRequest();
request.setContext(context);
+ request.setMethod("GET");
request.setParameter("SAMLResponse", samlResponse);
request.setParameter("RelayState", null);
request.setSession(session);
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/jbid-handlers.xml
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/jbid-handlers.xml
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/jbid-handlers.xml 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1,5 @@
+<Handlers xmlns="urn:jboss:identity-federation:handler:config:1.0">
+ <Handler
class="org.jboss.identity.federation.web.handlers.saml2.SAML2IssuerTrustHandler"/>
+ <Handler
class="org.jboss.identity.federation.web.handlers.saml2.SAML2LogOutHandler"/>
+ <Handler
class="org.jboss.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler"/>
+</Handlers>
\ No newline at end of file
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/jboss-idfed.xml
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/jboss-idfed.xml
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/WEB-INF/jboss-idfed.xml 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1,9 @@
+<JBossIDP xmlns="urn:jboss:identity-federation:config:1.0"
+ AttributeManager=""
+
RoleGenerator="org.jboss.identity.federation.core.impl.EmptyRoleGenerator">
+<IdentityURL>http://localhost:8080/idp/</IdentityURL>
+<Trust>
+ <Domains>localhost,jboss.com,jboss.org</Domains>
+</Trust>
+
+</JBossIDP>
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/roles.properties
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/roles.properties
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/idp/roles.properties 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1 @@
+manager=manager
\ No newline at end of file
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/jbid-handlers.xml
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/jbid-handlers.xml
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/jbid-handlers.xml 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1,4 @@
+<Handlers xmlns="urn:jboss:identity-federation:handler:config:1.0">
+ <Handler
class="org.jboss.identity.federation.web.handlers.saml2.SAML2LogOutHandler"/>
+ <Handler
class="org.jboss.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler"/>
+</Handlers>
\ No newline at end of file
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/jboss-idfed.xml
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/jboss-idfed.xml
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/WEB-INF/jboss-idfed.xml 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1,6 @@
+<JBossSP xmlns="urn:jboss:identity-federation:config:1.0"
+ AttributeManager="">
+<IdentityURL>http://localhost:8080/idp/</IdentityURL>
+<ServiceURL>http://localhost:8080/employee/</ServiceURL>
+
+</JBossSP>
\ No newline at end of file
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/roles.properties
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/roles.properties
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/employee/roles.properties 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1 @@
+manager=manager
\ No newline at end of file
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/jbid-handlers.xml
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/jbid-handlers.xml
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/jbid-handlers.xml 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1,4 @@
+<Handlers xmlns="urn:jboss:identity-federation:handler:config:1.0">
+ <Handler
class="org.jboss.identity.federation.web.handlers.saml2.SAML2LogOutHandler"/>
+ <Handler
class="org.jboss.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler"/>
+</Handlers>
\ No newline at end of file
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/jboss-idfed.xml
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/jboss-idfed.xml
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/WEB-INF/jboss-idfed.xml 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1,6 @@
+<JBossSP xmlns="urn:jboss:identity-federation:config:1.0"
+ AttributeManager="">
+<IdentityURL>http://localhost:8080/idp/</IdentityURL>
+<ServiceURL>http://localhost:8080/sales/</ServiceURL>
+
+</JBossSP>
\ No newline at end of file
Added:
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/roles.properties
===================================================================
---
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/roles.properties
(rev 0)
+++
identity-federation/trunk/jboss-identity-bindings/src/test/resources/saml2/redirect/sp/sales/roles.properties 2009-10-22
04:12:47 UTC (rev 867)
@@ -0,0 +1 @@
+manager=manager
\ No newline at end of file
Modified:
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/constants/GeneralConstants.java
===================================================================
---
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/constants/GeneralConstants.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/constants/GeneralConstants.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -51,6 +51,7 @@
String ROLE_GENERATOR = "ROLE_GENERATOR";
String ROLE_VALIDATOR = "ROLE_VALIDATOR";
+ String ROLE_VALIDATOR_IGNORE = "ROLE_VALIDATOR_IGNORE";
String SENDER_PUBLIC_KEY = "SENDER_PUBLIC_KEY";
String SIGN_OUTGOING_MESSAGES = "SIGN_OUTGOING_MESSAGES";
Modified:
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/filters/SPFilter.java
===================================================================
---
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/filters/SPFilter.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/filters/SPFilter.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -325,31 +325,7 @@
this.sendToDestination(samlResponseDocument, relayState, destination,
response, willSendRequest);
return;
}
- /*ResponseType responseType = saml2Response.getResponseType(is);
- SAMLDocumentHolder samlDocumentHolder =
saml2Response.getSamlDocumentHolder();
-
- boolean validSignature = this.verifySignature(samlDocumentHolder);
-
- if(validSignature == false)
- throw new IssuerNotTrustedException("Signature in saml document is
invalid");
-
- this.isTrusted(responseType.getIssuer().getValue());
-
- List<Object> assertions =
responseType.getAssertionOrEncryptedAssertion();
- if(assertions.size() == 0)
- throw new IllegalStateException("No assertions in reply from
IDP");
-
- Object assertion = assertions.get(0);
- if(assertion instanceof EncryptedElementType)
- {
- responseType = this.decryptAssertion(responseType);
- }
-
- userPrincipal = handleSAMLResponse(request, responseType);
- if(userPrincipal == null)
- response.sendError(HttpServletResponse.SC_FORBIDDEN);*/
-
//See if the session has been invalidated
try
{
@@ -373,18 +349,7 @@
log.trace("Server Exception:", e);
throw new ServletException("Server Exception");
}
- /*catch (IssuerNotTrustedException e)
- {
- if(trace)
- log.trace("IssuerNotTrustedException:", e);
- throw new ServletException("Issuer Not Trusted Exception");
- }
- catch (AssertionExpiredException e)
- {
- if(trace)
- log.trace("AssertionExpiredException:", e);
- throw new ServletException("Assertion expired Exception");
- } */
+
}
if(isNotNull(samlRequest))
Modified:
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/handlers/saml2/SAML2AuthenticationHandler.java
===================================================================
---
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/handlers/saml2/SAML2AuthenticationHandler.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/handlers/saml2/SAML2AuthenticationHandler.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -117,10 +117,12 @@
if(getType() == HANDLER_TYPE.IDP)
{
idp.generateSAMLRequest(request, response);
+ response.setSendRequest(true);
}
else
{
sp.generateSAMLRequest(request, response);
+ response.setSendRequest(true);
}
}
@@ -148,6 +150,9 @@
AuthnRequestType art = (AuthnRequestType) request.getSAML2Object();
HttpSession session = BaseSAML2Handler.getHttpSession(request);
Principal userPrincipal = (Principal)
session.getAttribute(GeneralConstants.PRINCIPAL_ID);
+ if(userPrincipal == null)
+ userPrincipal = httpContext.getRequest().getUserPrincipal();
+
List<String> roles = (List<String>)
session.getAttribute(GeneralConstants.ROLES_ID);
try
{
@@ -266,6 +271,7 @@
issuerValue, response.getDestination(), issuerValue);
response.setResultingDocument(samlRequest.convert(authn));
+ response.setSendRequest(true);
}
catch (Exception e)
{
@@ -368,19 +374,23 @@
{
return userName;
}
- };
- //Validate the roles
- IRoleValidator roleValidator =
- (IRoleValidator)
handlerChainConfig.getParameter(GeneralConstants.ROLE_VALIDATOR);
- if(roleValidator == null)
- throw new ProcessingException("Role Validator not provided");
-
- boolean validRole = roleValidator.userInRole(principal, roles);
- if(!validRole)
+ };
+
+ if(handlerChainConfig.getParameter(GeneralConstants.ROLE_VALIDATOR_IGNORE) ==
null)
{
- if(trace)
- log.trace("Invalid role:" + roles);
- principal = null;
+ //Validate the roles
+ IRoleValidator roleValidator =
+ (IRoleValidator)
handlerChainConfig.getParameter(GeneralConstants.ROLE_VALIDATOR);
+ if(roleValidator == null)
+ throw new ProcessingException("Role Validator not provided");
+
+ boolean validRole = roleValidator.userInRole(principal, roles);
+ if(!validRole)
+ {
+ if(trace)
+ log.trace("Invalid role:" + roles);
+ principal = null;
+ }
}
return principal;
}
Modified:
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/servlets/IDPServlet.java
===================================================================
---
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/servlets/IDPServlet.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/servlets/IDPServlet.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -514,7 +514,7 @@
}
return;
- }
+ }
}
protected void sendErrorResponseToSP(String referrer, HttpServletResponse response,
String relayState,
Modified:
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/util/IDPWebRequestUtil.java
===================================================================
---
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/util/IDPWebRequestUtil.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/util/IDPWebRequestUtil.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -359,7 +359,7 @@
relayState = RedirectBindingUtil.urlEncode(relayState);
String finalDest = destination + getDestination(urlEncodedResponse, relayState,
- supportSignature);
+ supportSignature, sendRequest);
if(trace) log.trace("Redirecting to="+ finalDest);
HTTPRedirectUtil.sendRedirectForResponder(finalDest, response);
}
@@ -380,7 +380,7 @@
* @return
*/
public String getDestination(String urlEncodedResponse, String urlEncodedRelayState,
- boolean supportSignature)
+ boolean supportSignature, boolean sendRequest)
{
StringBuilder sb = new StringBuilder();
@@ -398,7 +398,10 @@
}
else
{
- sb.append("?SAMLResponse=").append(urlEncodedResponse);
+ if(sendRequest)
+ sb.append("?SAMLRequest=").append(urlEncodedResponse);
+ else
+ sb.append("?SAMLResponse=").append(urlEncodedResponse);
if (urlEncodedRelayState != null && urlEncodedRelayState.length() >
0)
sb.append("&RelayState=").append(urlEncodedRelayState);
}
Modified:
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/util/RedirectBindingUtil.java
===================================================================
---
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/util/RedirectBindingUtil.java 2009-10-21
21:00:58 UTC (rev 866)
+++
identity-federation/trunk/jboss-identity-web/src/main/java/org/jboss/identity/federation/web/util/RedirectBindingUtil.java 2009-10-22
04:12:47 UTC (rev 867)
@@ -106,6 +106,18 @@
}
/**
+ * Apply deflate compression followed by base64 encoding
+ * @param stringToEncode
+ * @return
+ * @throws IOException
+ */
+ public static String deflateBase64Encode(byte[] stringToEncode) throws IOException
+ {
+ byte[] deflatedMsg = DeflateUtil.encode(stringToEncode);
+ return Base64.encodeBytes(deflatedMsg);
+ }
+
+ /**
* Apply URL decoding, followed by base64 decoding followed by deflate decompression
* @param encodedString
* @return