httpd_integration_test.py 10.3 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

144
145
146
147
        # Set to a non-empty string when testing the SSOGroups directive
        # (normally only the requested groups are generated).
        #extra_groups = "&g=group1,group2,group3"
        extra_groups = ''
ale's avatar
ale committed
148

ale's avatar
ale committed
149
150
151
152
153
        # Tests have a name so that we can recognize failures.
        checks = [
            ("index -> redirect",
             {"url": "/index.html", 
              "status": 302, 
ale's avatar
ale committed
154
              "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
155
156
157
158
159
160
161
162
163
            ("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
164
              "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
165
166
167
168

            ("protected-user -> redirect",
             {"url": "/protected-user/index.html", 
              "status": 302, 
ale's avatar
ale committed
169
              "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
170
171
172
173
174
175
176
177
178
179
180
181
182
            ("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
183
              "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
184
185
186
187
188
            ("protected-group with cookie -> ok",
             {"url": "/protected-group/index.html",
              "cookie": mkcookie(self._ticket()),
              "status": 200,
              "body": "ok"}),
189
190
191
192
            #("protected-group with cookie wrong group -> unauthorized",
            # {"url": "/protected-group/index.html",
            #  "cookie": mkcookie(self._ticket(group="group2")),
            #  "status": 401}),
ale's avatar
ale committed
193
194
195
196
            ("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
197
              "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
198
199
200
201
202

            ("other-service -> redirect",
             {"url": "/other-service/index.html",
              "status": 302,
              "http_host": "testhost.example.com",
ale's avatar
ale committed
203
              "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}),
204
205
206
207

            ("protected-htaccess -> redirect",
             {"url": "/protected-htaccess/index.html",
              "status": 302,
ale's avatar
ale committed
208
              "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}),
209
210
            ("protected-htaccess with cookie -> ok",
             {"url": "/protected-htaccess/index.html",
211
              "cookie": mkcookie(self._ticket(service="service.example.com/protected-htaccess/")),
212
213
214
215
              "status": 200,
              "body": "ok"}),
            ("protected-htaccess with cookie wrong user -> fail",
             {"url": "/protected-htaccess/index.html",
216
              "cookie": mkcookie(self._ticket(user="wronguser", service="service.example.com/protected-htaccess/")),
217
              "status": 401 }),
ale's avatar
ale committed
218
219
            ]
        for name, check in checks:
ale's avatar
ale committed
220
            for i in xrange(10):
221
                print 'CHECKING %s (%d of 10)' % (name, i), check
ale's avatar
ale committed
222
223
224
                status, body, location = _query(check["url"],
                                                host=check.get("http_host"),
                                                cookie=check.get("cookie"))
ale's avatar
ale committed
225
                self.assertEquals(
ale's avatar
ale committed
226
227
228
229
230
231
232
233
234
235
236
237
238
                    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
239
240

        # test that environment variables are correctly set
241
242
        status, body, location = _query("/cgi/env.cgi",
                                        cookie=mkcookie(self._ticket()))
ale's avatar
ale committed
243
244
245
246
247
248
249
250
251
        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(
252
           {"t": tkt, "d": "https://service.example.com/index.html"}))
ale's avatar
ale committed
253
254
255
256
        resp = conn.getresponse()
        self.assertEquals(302, resp.status)
        set_cookie = resp.getheader("Set-Cookie")
        self.assertTrue(set_cookie)
257
        self.assertTrue(tkt in set_cookie)
ale's avatar
ale committed
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
        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()