Add support for IdP-initiated login
authorRob Crittenden <rcritten@redhat.com>
Wed, 28 Oct 2015 20:29:57 +0000 (16:29 -0400)
committerPatrick Uiterwijk <puiterwijk@redhat.com>
Sat, 14 Nov 2015 00:09:50 +0000 (01:09 +0100)
This uses the Redirect SSO endpoint and two new optional
arguments: SPIdentifier and RelayState.

SPIdentifier is the provider ID of the SP.
RelayState is where on the SP the user should be sent.

If the user is already authenticted then a SAMLResponse is generated
and the existing HTML page is generated and sent to the user including
this response and the value of RelayState (if any). This will then POST
to the SP and the user will be show the page on the SP.

If the user is not authenticated then they will be given the login page
after which they will be sent to the SP.

The link to the SP on the IdP Portal has changed to be and IdP-initiated
login. If a user bookmarks this link then they will always go to that
SP and be authenticated first, if needed.

https://fedorahosted.org/ipsilon/ticket/138

Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-by: John Dennis <jdennis@redhat.com>
ipsilon/providers/saml2/auth.py
ipsilon/providers/saml2idp.py
templates/index.html

index 940746c..cc41bb8 100644 (file)
@@ -99,23 +99,65 @@ class AuthenticateRequest(ProviderPageBase):
 
         return login
 
-    def saml2login(self, request):
+    def _idp_initiated_login(self, spidentifier, relaystate):
+        """
+        Perform an Idp-initiated login
 
-        if not request:
+        Exceptions are handled by the caller
+        """
+        login = self.cfg.idp.get_login_handler()
+
+        login.initIdpInitiatedAuthnRequest(spidentifier)
+
+        # Hardcode for now, handle Artifact later
+        login.request.protocolBinding = lasso.SAML2_METADATA_BINDING_POST
+
+        login.processAuthnRequestMsg()
+
+        if relaystate is not None:
+            login.msgRelayState = relaystate
+        else:
+            provider = ServiceProvider(self.cfg, login.remoteProviderId)
+            if provider.splink is not None:
+                login.msgRelayState = provider.splink
+            else:
+                login.msgRelayState = login.remoteProviderId
+
+        return login
+
+    def saml2login(self, request, spidentifier=None, relaystate=None):
+        """
+        request: the SAML request
+        spidentifier: the provider ID for IdP-initiated login
+        relaystate: optional string to direct user to particular place on
+                    the SP after sending POST. If one is not provided then
+                    the protected site from the SP is used, otherwise it
+                    is set to the remote provider ID.
+        """
+        if not request and not spidentifier:
             raise cherrypy.HTTPError(400,
                                      'SAML request token missing or empty')
 
-        try:
-            login = self._parse_request(request)
-        except InvalidRequest, e:
-            self.debug(str(e))
-            raise cherrypy.HTTPError(400, 'Invalid SAML request token')
-        except UnknownProvider, e:
-            self.debug(str(e))
-            raise cherrypy.HTTPError(400, 'Unknown Service Provider')
-        except Exception, e:  # pylint: disable=broad-except
-            self.debug(str(e))
-            raise cherrypy.HTTPError(500)
+        if spidentifier:
+            try:
+                login = self._idp_initiated_login(spidentifier, relaystate)
+            except lasso.ServerProviderNotFoundError:
+                raise cherrypy.HTTPError(400, 'Unknown Service Provider')
+            except Exception, e:  # pylint: disable=broad-except
+                self.debug(str(e))
+                raise cherrypy.HTTPError(500)
+        else:
+            try:
+                login = self._parse_request(request)
+            except InvalidRequest, e:
+                self.debug(str(e))
+                raise cherrypy.HTTPError(400, 'Invalid SAML request token')
+            except UnknownProvider, e:
+                self.debug(str(e))
+                raise cherrypy.HTTPError(400, 'Unknown Service Provider')
+            except Exception, e:  # pylint: disable=broad-except
+                self.debug(str(e))
+                raise cherrypy.HTTPError(500)
 
         return login
 
index 3ed95d8..78e7778 100644 (file)
@@ -76,7 +76,10 @@ class Redirect(AuthenticateRequest):
 
         query = cherrypy.request.query_string
 
-        login = self.saml2login(query)
+        spidentifier = kwargs.get('SPIdentifier')
+        relaystate = kwargs.get(lasso.SAML2_FIELD_RELAYSTATE)
+
+        login = self.saml2login(query, spidentifier, relaystate)
         return self.auth(login)
 
 
index db1339b..5215b3b 100644 (file)
@@ -90,7 +90,7 @@
         <div class="col-sm-4 col-md-3 provider">
           <a
              {% if p.splink or 0 %}
-               href="{{ p.splink }}"
+               href="{{ basepath }}/saml2/SSO/Redirect?SPIdentifier={{ p.provider_id }}&RelayState={{ p.splink }}"
              {% else %}
                href="#"
              {% endif %}