From d723867fb3c3ecff9848a472c7cfad2d7756d82b Mon Sep 17 00:00:00 2001
From: Benjamin Dauvergne <bdauvergne@entrouvert.com>
Date: Mon, 15 Apr 2019 15:53:17 +0200
Subject: [PATCH] root: implement automatic tryauth (#12867)

---
 tests/test_saml_auth.py | 27 +++++++++++++++++++++++++++
 wcs/root.py             | 35 +++++++++++++++++++++++++++++++++--
 2 files changed, 60 insertions(+), 2 deletions(-)

diff --git a/tests/test_saml_auth.py b/tests/test_saml_auth.py
index 88aea69c..793d4996 100644
--- a/tests/test_saml_auth.py
+++ b/tests/test_saml_auth.py
@@ -443,3 +443,30 @@ def test_saml_idp_logout(pub):
     saml2.slo_idp(urlparse.urlparse(logout.msgUrl).query)
     assert req.response.headers['location'].startswith('http://sso.example.net/saml2/slo_return?SAMLResponse=')
     assert req.session is None
+
+
+def test_opened_session_cookie(pub):
+    app = get_app(pub)
+    app.set_cookie('IDP_OPENED_SESSION', '1')
+    resp = app.get('/')
+    assert resp.status_int == 200
+    pub.site_options.set('options', 'idp_session_cookie_name', 'IDP_OPENED_SESSION')
+    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
+        pub.site_options.write(fd)
+
+    resp = app.get('/?parameter=value')
+    assert 'secure' in resp.headers['Set-Cookie']
+    assert 'httponly' in resp.headers['Set-Cookie']
+    assert 'path=/' in resp.headers['Set-Cookie']
+    assert resp.status_int == 302
+    assert resp.location == 'http://example.net/login/?ReturnUrl=http%3A//example.net/%3Fparameter%3Dvalue&IsPassive=true'
+    cookie_name = '%s-passive-auth-tried' % pub.config.session_cookie_name
+    assert cookie_name in app.cookies
+
+
+def test_no_opened_session_cookie(pub):
+    app = get_app(pub)
+    resp = app.get('/')
+    assert resp.status_int == 200
+    cookie_name = '%s-passive-auth-tried' % pub.config.session_cookie_name
+    assert cookie_name not in app.cookies
diff --git a/wcs/root.py b/wcs/root.py
index cb5af694..129a8352 100644
--- a/wcs/root.py
+++ b/wcs/root.py
@@ -21,7 +21,7 @@ import re
 
 from django.utils.six.moves.urllib import parse as urllib
 
-from quixote import get_publisher, get_response, get_session, redirect, get_session_manager
+from quixote import get_publisher, get_response, get_session, redirect, get_session_manager, get_request
 from quixote.directory import Directory
 from quixote.html import htmltext, TemplateIO
 from quixote.util import StaticDirectory
@@ -343,7 +343,38 @@ class RootDirectory(Directory):
         except errors.TraversalError:
             pass
 
-        return root.RootDirectory()._q_traverse(path)
+        output = root.RootDirectory()._q_traverse(path)
+        return self.automatic_sso(output)
+
+    def automatic_sso(self, output):
+        request = get_request()
+        response = get_response()
+
+        publisher = get_publisher()
+        OPENED_SESSION_COOKIE = publisher.get_site_option('idp_session_cookie_name')
+        PASSIVE_TRIED_COOKIE = '%s-passive-auth-tried' % publisher.config.session_cookie_name
+        if OPENED_SESSION_COOKIE not in request.cookies and PASSIVE_TRIED_COOKIE in request.cookies:
+            response.expire_cookie(PASSIVE_TRIED_COOKIE)
+            return output
+        elif OPENED_SESSION_COOKIE in request.cookies and PASSIVE_TRIED_COOKIE not in request.cookies:
+            ident_methods = get_cfg('identification', {}).get('methods', [])
+            idps = get_cfg('idp', {})
+            if request.user:
+                return output
+            if len(idps) != 1:
+                return output
+            if ident_methods and 'idp' not in ident_methods:
+                return output
+            response.set_cookie(PASSIVE_TRIED_COOKIE, '1', secure=1, httponly=1,
+                                path=publisher.config.session_cookie_path,
+                                domain=publisher.config.session_cookie_domain)
+            url = request.get_url()
+            query = request.get_query()
+            if query:
+                url += '?' + query
+            return root.tryauth(url)
+        else:
+            return output
 
     def _q_lookup(self, component):
         # is this a category ?
-- 
2.27.0.rc0

