httpd_integration_test.py 10.1 KB
Newer Older
ale's avatar
ale committed
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python

import httplib
import os
import subprocess
import sys
import time
import unittest
import urllib
sys.path.append("../../python")
import sso

13
14
# Allow overriding the apache2 executable location from the environment.
APACHE_BIN = os.getenv('APACHE_BIN', '/usr/sbin/apache2')
15
16
17
18
APXS_BIN = os.getenv('APXS_BIN', '/usr/bin/apxs2')
for exe in (APACHE_BIN, APXS_BIN):
    if not os.path.exists(exe):
        raise Exception('%s not found, this test cannot run' % exe)
19

ale's avatar
ale committed
20
21
22
# Use 2.4 ocnfiguration.
APACHE_CONFIG = 'test-httpd-2.4.conf'

ale's avatar
ale committed
23
24
devnull = open(os.devnull)

25
26
27
28

def _start_httpd(public_key):
    with open('public.key', 'w') as fd:
        fd.write(public_key)
29
30
31
    env = dict(os.environ)
    env['TESTROOT'] = os.getcwd()
    env['MODULEDIR'] = subprocess.check_output(
ale's avatar
ale committed
32
        [APXS_BIN, '-q', 'LIBEXECDIR'], stderr=devnull).strip()
ale's avatar
ale committed
33
    cmd = [APACHE_BIN, "-f", os.path.join(os.getcwd(), APACHE_CONFIG), "-X"]
ale's avatar
ale committed
34
35
36

    if os.getenv('STRACE'):
        cmd = ['strace', '-s', '256', '-f'] + cmd
ale's avatar
ale committed
37
38
    if os.getenv('VALGRIND'):
        cmd = ['valgrind'] + cmd
ale's avatar
ale committed
39

40
    httpd = subprocess.Popen(cmd, env=env)
41
42
    print 'httpd pid:', httpd.pid
    time.sleep(1)
ale's avatar
ale committed
43
44
    if httpd.poll():
        raise Exception('httpd failed to start!')
45
46
47
48
    return httpd


def _stop_httpd(httpd):
ale's avatar
ale committed
49
    httpd.terminate()
50
51
    time.sleep(1)
    try:
ale's avatar
ale committed
52
        httpd.kill()
53
54
    except OSError:
        pass
ale's avatar
ale committed
55
56
57
58
59
60
61
62
63

    status = httpd.wait()
    if os.WIFEXITED(status):
        print 'httpd exited with status %d' % os.WEXITSTATUS(status)
    elif os.WIFSIGNALED(status):
        print 'httpd exited due to signal %d' % os.WTERMSIG(status)
    else:
        print 'httpd exited for unknown reason (returncode=%d)' % status

64
65
66
67
    try:
        os.remove("test-httpd.pid")
    except OSError:
        pass
ale's avatar
ale committed
68
69
70
71
    try:
        os.remove("public.key")
    except OSError:
        pass
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112


def _query(url, host=None, cookie=None):
    conn = httplib.HTTPConnection("127.0.0.1", 33000)
    headers = {"Host": host or "localhost"}
    if cookie:
        headers["Cookie"] = cookie
    conn.request("GET", url, headers=headers)
    resp = conn.getresponse()
    location = None
    body = None
    if resp.status in (301, 302):
        location = resp.getheader("Location")
    elif resp.status == 200:
        body = resp.read()
    conn.close()
    return (resp.status, body, location)


class HttpdKeyLoadingTest(unittest.TestCase):

    def testKeyWithNullByte(self):

        has_null_byte = lambda s: s.find('\0') >= 0

        public = ''
        while not has_null_byte(public):
            public, secret = sso.generate_keys()
        print "public key:", ['%02x' % ord(x) for x in public]

        httpd = _start_httpd(public)
        try:
            signer = sso.Signer(secret)
            t = sso.Ticket("testuser", "service.example.com/", "example.com",
                           set(["group1"]))
            ts = signer.sign(t)
            status, body, location = _query("/index.html", 
                                            cookie="SSO_test=%s" % ts)
            self.assertEquals(200, status)
        finally:
            _stop_httpd(httpd)
ale's avatar
ale committed
113
114
115
116
117
118
119


class HttpdIntegrationTest(unittest.TestCase):

    def setUp(self):
        self.public, self.secret = sso.generate_keys()
        self.signer = sso.Signer(self.secret)
120
        self.httpd = _start_httpd(self.public)
ale's avatar
ale committed
121
122

    def tearDown(self):
123
        _stop_httpd(self.httpd)
ale's avatar
ale committed
124

125
126
    def _ticket(self, user="testuser", group="group1", service="service.example.com/"):
        t = sso.Ticket(user, service, "example.com", set([group]))
ale's avatar
ale committed
127
128
129
130
        signedt = self.signer.sign(t)
        print 'ticket:', signedt
        return signedt

131
132
133
134
135
136
    def testManyRequests(self):
        n = 100
        errors = 0
        for i in xrange(n):
            cookie = 'SSO_test=%s' % self._ticket()
            status, body, location = _query("/index.html", cookie=cookie)
ale's avatar
ale committed
137
            self.assertEquals(200, status)
ale's avatar
ale committed
138
139
140
141

    def testRedirectionUrls(self):

        def mkcookie(tkt):
142
            return "SSO_test=%s" % tkt
ale's avatar
ale committed
143

ale's avatar
ale committed
144
145
146
147
        # For Apache 2.2, set this to the empty string (we do not use the
        # SSOGroup directive, so only the requested groups are generated).
        extra_groups = "&g=group1,group2,group3"

ale's avatar
ale committed
148
149
150
151
152
        # Tests have a name so that we can recognize failures.
        checks = [
            ("index -> redirect",
             {"url": "/index.html", 
              "status": 302, 
ale's avatar
ale committed
153
              "location": "https://login.example.com/?s=service.example.com%2F&d=https%3A%2F%2Fservice.example.com%2Findex.html" + extra_groups}),
ale's avatar
ale committed
154
155
156
157
158
159
160
161
162
            ("index with cookie -> ok",
             {"url": "/index.html",
              "cookie": mkcookie(self._ticket()),
              "status": 200,
              "body": "ok"}),
            ("index with malformed cookie -> redirect",
             {"url": "/index.html",
              "cookie": mkcookie('blahblah' * 8),
              "status": 302,
ale's avatar
ale committed
163
              "location": "https://login.example.com/?s=service.example.com%2F&d=https%3A%2F%2Fservice.example.com%2Findex.html" + extra_groups}),
ale's avatar
ale committed
164
165
166
167

            ("protected-user -> redirect",
             {"url": "/protected-user/index.html", 
              "status": 302, 
ale's avatar
ale committed
168
              "location": "https://login.example.com/?s=service.example.com%2F&d=https%3A%2F%2Fservice.example.com%2Fprotected-user%2Findex.html" + extra_groups}),
ale's avatar
ale committed
169
170
171
172
173
174
175
176
177
178
179
180
181
            ("protected-user with cookie -> ok",
             {"url": "/protected-user/index.html",
              "cookie": mkcookie(self._ticket()),
              "status": 200,
              "body": "ok"}),
            ("protected-user with cookie wrong user -> fail",
             {"url": "/protected-user/index.html",
              "cookie": mkcookie(self._ticket(user="wronguser")),
              "status": 401 }),

            ("protected-group -> redirect",
             {"url": "/protected-group/index.html", 
              "status": 302, 
ale's avatar
ale committed
182
              "location": "https://login.example.com/?s=service.example.com%2F&d=https%3A%2F%2Fservice.example.com%2Fprotected-group%2Findex.html" + (extra_groups if extra_groups else "&g=group1")}),
ale's avatar
ale committed
183
184
185
186
187
188
189
190
191
            ("protected-group with cookie -> ok",
             {"url": "/protected-group/index.html",
              "cookie": mkcookie(self._ticket()),
              "status": 200,
              "body": "ok"}),
            ("protected-group with cookie wrong group -> redirect",
             {"url": "/protected-group/index.html",
              "cookie": mkcookie(self._ticket(group="group2")),
              "status": 302,
ale's avatar
ale committed
192
              "location": "https://login.example.com/?s=service.example.com%2F&d=https%3A%2F%2Fservice.example.com%2Fprotected-group%2Findex.html" + (extra_groups if extra_groups else "&g=group1")}),
ale's avatar
ale committed
193
194
195
196
197

            ("other-service -> redirect",
             {"url": "/other-service/index.html",
              "status": 302,
              "http_host": "testhost.example.com",
ale's avatar
ale committed
198
              "location": "https://login.example.com/?s=testhost.example.com%2Fother-service%2F&d=https%3A%2F%2Ftesthost.example.com%2Fother-service%2Findex.html" + extra_groups}),
199
200
201
202

            ("protected-htaccess -> redirect",
             {"url": "/protected-htaccess/index.html",
              "status": 302,
ale's avatar
ale committed
203
              "location": "https://login.example.com/?s=service.example.com%2Fprotected-htaccess%2F&d=https%3A%2F%2Fservice.example.com%2Fprotected-htaccess%2Findex.html" + extra_groups}),
204
205
            ("protected-htaccess with cookie -> ok",
             {"url": "/protected-htaccess/index.html",
206
              "cookie": mkcookie(self._ticket(service="service.example.com/protected-htaccess/")),
207
208
209
210
              "status": 200,
              "body": "ok"}),
            ("protected-htaccess with cookie wrong user -> fail",
             {"url": "/protected-htaccess/index.html",
211
              "cookie": mkcookie(self._ticket(user="wronguser", service="service.example.com/protected-htaccess/")),
212
              "status": 401 }),
ale's avatar
ale committed
213
214
            ]
        for name, check in checks:
ale's avatar
ale committed
215
            for i in xrange(10):
ale's avatar
ale committed
216
                print 'CHECKING', check
ale's avatar
ale committed
217
218
219
                status, body, location = _query(check["url"],
                                                host=check.get("http_host"),
                                                cookie=check.get("cookie"))
ale's avatar
ale committed
220
                self.assertEquals(
ale's avatar
ale committed
221
222
223
224
225
226
227
228
229
230
231
232
233
                    check["status"], status,
                    "test: '%s'\nunexpected HTTP status for %s (got %d, exp %d)" % (
                        name, check["url"], status, check["status"]))
                if "body" in check:
                    self.assertTrue(
                        check["body"] in body,
                        "test: '%s'\nbody mismatch for %s (exp '%s')" % (
                            name, check["url"], check["body"]))
                if "location" in check:
                    self.assertEquals(
                        check["location"], location,
                        "test: '%s'\nunexpected redirect for %s (got %s, exp %s)" % (
                            name, check["url"], location, check["location"]))
ale's avatar
ale committed
234
235

        # test that environment variables are correctly set
236
237
        status, body, location = _query("/cgi/env.cgi",
                                        cookie=mkcookie(self._ticket()))
ale's avatar
ale committed
238
239
240
241
242
243
244
245
246
        self.assertEquals(200, status)
        self.assertTrue(body)
        self.assertTrue("REMOTE_USER=testuser" in body)
        self.assertTrue("SSO_SERVICE=service.example.com/" in body)

        # test the /sso_login endpoint
        conn = httplib.HTTPConnection("127.0.0.1", 33000)
        tkt = self._ticket()
        conn.request("GET", "/sso_login?%s" % urllib.urlencode(
247
           {"t": tkt, "d": "https://service.example.com/index.html"}))
ale's avatar
ale committed
248
249
250
251
        resp = conn.getresponse()
        self.assertEquals(302, resp.status)
        set_cookie = resp.getheader("Set-Cookie")
        self.assertTrue(set_cookie)
252
        self.assertTrue(tkt in set_cookie)
ale's avatar
ale committed
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
        conn.close()

        # test the /sso_logout endpoint
        conn = httplib.HTTPConnection("127.0.0.1", 33000)
        conn.request("GET", "/sso_logout", headers={
	  "Cookie": mkcookie(tkt)})
        resp = conn.getresponse()
        set_cookie = resp.getheader("Set-Cookie")
        self.assertTrue(set_cookie)
        self.assertTrue("SSO_test=;" in set_cookie)
        conn.close()


if __name__ == "__main__":
    unittest.main()