~launchpad-pqm/launchpad/devel

10847.2.1 by Gary Poster
log out from bzr and openid after logging out from Launchpad
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4
import unittest
5
import urllib
6
7
import lazr.uri
8
import wsgi_intercept
9
from wsgi_intercept.urllib2_intercept import install_opener, uninstall_opener
10
import wsgi_intercept.zope_testbrowser
11
from paste.httpexceptions import HTTPExceptionHandler
12
13
from canonical.config import config
14
from canonical.launchpad.webapp.vhosts import allvhosts
11666.3.5 by Curtis Hovey
Import layers from canonical.testing.layers.
15
from canonical.testing.layers import DatabaseFunctionalLayer
10847.2.1 by Gary Poster
log out from bzr and openid after logging out from Launchpad
16
from launchpad_loggerhead.app import RootApp
17
from launchpad_loggerhead.session import SessionHandler
18
from lp.testing import TestCase
19
20
SESSION_VAR = 'lh.session'
21
22
# See sourcecode/launchpad-loggerhead/start-loggerhead.py for the production
23
# mechanism for getting the secret.
24
SECRET = 'secret'
25
26
27
def session_scribbler(app, test):
28
    """Squirrel away the session variable."""
29
    def scribble(environ, start_response):
30
        test.session = environ[SESSION_VAR] # Yay for mutables.
31
        return app(environ, start_response)
32
    return scribble
33
34
35
def dummy_destination(environ, start_response):
36
    """Return a fake response."""
37
    start_response('200 OK', [('Content-type','text/plain')])
38
    return ['This is a dummy destination.\n']
39
40
41
class SimpleLogInRootApp(RootApp):
42
    """A mock root app that doesn't require open id."""
43
    def _complete_login(self, environ, start_response):
44
        environ[SESSION_VAR]['user'] = 'bob'
45
        start_response('200 OK', [('Content-type','text/plain')])
46
        return ['\n']
47
48
49
class TestLogout(TestCase):
50
    layer = DatabaseFunctionalLayer
51
52
    def intercept(self, uri, app):
53
        """Install wsgi interceptors for the uri, app tuple."""
54
        if isinstance(uri, basestring):
55
            uri = lazr.uri.URI(uri)
56
        port = uri.port
57
        if port is None:
58
            if uri.scheme == 'http':
59
                port = 80
60
            elif uri.scheme == 'https':
61
                port = 443
62
            else:
63
                raise NotImplementedError(uri.scheme)
64
        else:
65
            port = int(port)
66
        wsgi_intercept.add_wsgi_intercept(uri.host, port, lambda: app)
67
        self.intercepted.append((uri.host, port))
68
69
    def setUp(self):
70
        TestCase.setUp(self)
71
        self.intercepted = []
72
        self.session = None
73
        self.root = app = SimpleLogInRootApp(SESSION_VAR)
74
        app = session_scribbler(app, self)
75
        app = HTTPExceptionHandler(app)
76
        app = SessionHandler(app, SESSION_VAR, SECRET)
77
        self.cookie_name = app.cookie_handler.cookie_name
78
        self.intercept(config.codehosting.codebrowse_root, app)
79
        self.intercept(config.codehosting.secure_codebrowse_root, app)
80
        self.intercept(allvhosts.configs['mainsite'].rooturl,
81
                       dummy_destination)
82
        install_opener()
83
        self.browser = wsgi_intercept.zope_testbrowser.WSGI_Browser()
84
        # We want to pretend we are not a robot, or else mechanize will honor
85
        # robots.txt.
86
        self.browser.mech_browser.set_handle_robots(False)
87
        self.browser.open(
88
            config.codehosting.secure_codebrowse_root + '+login')
89
90
    def tearDown(self):
91
        uninstall_opener()
92
        for host, port in self.intercepted:
93
            wsgi_intercept.remove_wsgi_intercept(host, port)
94
        TestCase.tearDown(self)
95
96
    def testLoggerheadLogout(self):
97
        # We start logged in as 'bob'.
98
        self.assertEqual(self.session['user'], 'bob')
99
        self.browser.open(
100
            config.codehosting.secure_codebrowse_root + 'favicon.ico')
101
        self.assertEqual(self.session['user'], 'bob')
102
        self.failUnless(self.browser.cookies.get(self.cookie_name))
103
104
        # When we visit +logout, our session is gone.
105
        self.browser.open(
106
            config.codehosting.secure_codebrowse_root + '+logout')
107
        self.assertEqual(self.session, {})
108
109
        # By default, we have been redirected to the Launchpad root.
110
        self.assertEqual(
111
            self.browser.url, allvhosts.configs['mainsite'].rooturl)
112
113
        # The session cookie still exists, because of how
114
        # paste.auth.cookie works (see
115
        # http://trac.pythonpaste.org/pythonpaste/ticket/139 ) but the user
116
        # does in fact have an empty session now.
117
        self.browser.open(
118
            config.codehosting.secure_codebrowse_root + 'favicon.ico')
119
        self.assertEqual(self.session, {})
120
121
    def testLoggerheadLogoutRedirect(self):
122
        # When we visit +logout with a 'next_to' value in the query string,
123
        # the logout page will redirect to the given URI.  As of this
124
        # writing, this is used by Launchpad to redirect to our OpenId
125
        # provider (see canonical.launchpad.tests.test_login.
126
        # TestLoginAndLogout.test_CookieLogoutPage).
127
128
        # Here, we will have a more useless example of the basic machinery.
129
        dummy_root = 'http://dummy.dev/'
130
        self.intercept(dummy_root, dummy_destination)
131
        self.browser.open(
132
            config.codehosting.secure_codebrowse_root +
133
            '+logout?' +
134
            urllib.urlencode(dict(next_to=dummy_root + '+logout')))
135
136
        # We are logged out, as before.
137
        self.assertEqual(self.session, {})
138
139
        # Now, though, we are redirected to the ``next_to`` destination.
140
        self.assertEqual(self.browser.url, dummy_root + '+logout')
141
        self.assertEqual(self.browser.contents,
142
                         'This is a dummy destination.\n')
143
144
145
def test_suite():
146
    return unittest.TestLoader().loadTestsFromName(__name__)