File: test_authservice.py

package info (click to toggle)
gracie 0.2.11-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 412 kB
  • ctags: 552
  • sloc: python: 3,851; sh: 62; makefile: 16
file content (352 lines) | stat: -rw-r--r-- 11,027 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# -*- coding: utf-8 -*-

# test/test_authservice.py
# Part of Gracie, an OpenID provider.
#
# Copyright © 2007–2010 Ben Finney <ben+python@benfinney.id.au>
# This is free software; you may copy, modify and/or distribute this work
# under the terms of the GNU General Public License, version 2 or later.
# No warranty expressed or implied. See the file ‘LICENSE.GPL-2’ for details.

""" Unit test for authservice module.
    """

import scaffold

from gracie import authservice


class ModuleExceptions_TestCase(scaffold.Exception_TestCase):
    """ Test cases for module exceptions. """

    def setUp(self):
        """ Set up test fixtures """
        self.valid_exceptions = {
            authservice.AuthenticationError: dict(
                min_args = 2,
                types = [EnvironmentError],
                ),
            }

        super(ModuleExceptions_TestCase, self).setUp()

    def test_autherror_string_contains_reason(self):
        """ AuthenticationError string should contain code and reason """
        code = 42
        reason = "Naughty!"
        instance = authservice.AuthenticationError(code, reason)
        str(instance)
        self.failUnlessOutputCheckerMatch(
            "...%(code)s..." % vars(), str(instance)
            )
        self.failUnlessOutputCheckerMatch(
            "...%(reason)s..." % vars(), str(instance)
            )


class BaseAuthService_TestCase(scaffold.TestCase):
    """ Test cases for AuthService class. """

    def setUp(self):
        """ Set up test fixtures """

        self.service_class = authservice.BaseAuthService

    def test_instantiate(self):
        """ New BaseAuthService instance should be created """
        instance = self.service_class()
        self.failIfIs(instance, None)

    def test_name_is_not_implemented(self):
        """ BaseAuthService should have name NotImplemented """
        instance = self.service_class()
        self.failUnlessEqual(NotImplemented, instance.name)

    def test_get_entry_is_not_implemented(self):
        """ BaseAuthService should not implement get_entry() """
        instance = self.service_class()
        name = "foo"
        self.failUnlessRaises(NotImplementedError,
            instance.get_entry, name
        )

    def test_authenticate_is_not_implemented(self):
        """ BaseAuthService should not implement authenticat() """
        instance = self.service_class()
        credentials = dict(
            username = "foo",
            password = "bar",
            )
        self.failUnlessRaises(
            NotImplementedError,
            instance.authenticate, credentials
            )


stub_entries = [
    dict(id=1000, name="fred", password="password1",
         fullname="Fred Nurk", comment="Fred Nurk"),
    dict(id=1010, name="bill", password="secret1",
         fullname="William Fosdycke", comment="William Fosdycke,,,"),
    ]

class Stub_AuthService(object):
    """ Stub class for AuthService classes """

    def get_entry(self, value):
        match = [e for e in stub_entries if e['name'] == value]
        if not match:
            raise KeyError("No entry for %(value)r" % vars())
        entry = match[0]
        return entry

    def authenticate(self, credentials):
        try:
            username = credentials['username']
            password = credentials['password']
            entry = self.get_entry(username)
        except KeyError, e:
            raise authservice.AuthenticationError(
                0, "User not found")
        if password != entry['password']:
            raise authservice.AuthenticationError(
                0, "Authentication failed")
        return username

class Stub_PwdModule(object):
    """ Stub class for a pwd module """

    _entries = [
        (e['name'], "*", e['id'], 500, e['comment'],
         "/home/"+e['name'], "/bin/sh")
        for e in stub_entries]

    _entries_by_name = dict([(e[0], e) for e in _entries])
    _entries_by_uid = dict([(e[2], e) for e in _entries])

    def getpwnam(self, name):
        """ Get an entry by account name """
        try:
            entry = self._entries_by_name[name]
        except KeyError:
            raise KeyError("name not found: %(name)s" % vars())
        return entry

    def getpwuid(self, uid):
        """ Get an entry by account ID """
        try:
            entry = self._entries_by_uid[uid]
        except KeyError:
            raise KeyError("uid not found: %(uid)s" % vars())
        return entry

class PosixAuthService_TestCase(scaffold.TestCase):
    """ Test cases for PosixAuthService class. """

    def setUp(self):
        """ Set up test fixtures """

        self.service_class = authservice.PosixAuthService

        self.pwd_module_prev = authservice.pwd
        self.pwd_module = Stub_PwdModule()
        authservice.pwd = self.pwd_module

    def tearDown(self):
        """ Tear down test fixtures """
        authservice.pwd = self.pwd_module_prev

    def test_instantiate(self):
        """ New PosixAuthService instance should be created """
        instance = self.service_class()
        self.failIfIs(instance, None)

    def test_get_entry_name_unknown_raises_keyerror(self):
        """ get_entry for a bogus name should raise KeyError """
        instance = self.service_class()
        name = "nosuchuser"
        self.failUnlessRaises(
            KeyError,
            instance.get_entry, name
            )

    def test_get_entry_name_known_returns_entry(self):
        """ get_entry for a known name should return an auth entry """
        instance = self.service_class()
        pwd_entry = self.pwd_module.getpwnam("fred")
        (name, _, uid, _, fullname, _, _) = pwd_entry
        expect_entry = dict(
            id = uid,
            name = name,
            fullname = fullname,
            )
        entry = instance.get_entry(name)
        self.failUnlessEqual(expect_entry, entry)

    def test_get_entry_strips_extra_info_from_comment(self):
        """ get_entry should strip extra info to get the fullname """
        instance = self.service_class()
        pwd_entry = self.pwd_module.getpwnam("bill")
        (name, _, uid, _, comment, _, _) = pwd_entry
        fullname = comment.split(",")[0]
        expect_entry = dict(
            id = uid,
            name = name,
            fullname = fullname,
            )
        entry = instance.get_entry(name)
        self.failUnlessEqual(expect_entry, entry)


class Stub_PamError(Exception):
    def __init__(self, code, reason):
        Exception.__init__(self, reason, code)

class Stub_PamAuth(object):
    """ Stub class for PAM authentication service """

    _entries = dict((e['name'], e) for e in stub_entries)

    def start(self, service):
        self._items = dict()

    def get_item(self, key):
        return self._items[key]

    def set_item(self, key, value):
        self._items[key] = value

    def _converse(self, query_list):
        conversation = self._items[PAM.PAM_CONV]
        response_list = conversation(self, query_list, None)
        return response_list

    def _get_auth_entry(self, username):
        try:
            entry = self._entries[username]
        except KeyError:
            raise PAM.error(
                PAM.PAM_USER_UNKNOWN,
                "User %(username)s not found" % vars()
                )
        return entry

    def authenticate(self, flags=0):
        username = self._items[PAM.PAM_USER]
        entry = self._get_auth_entry(username)

        query_list = []
        query_list.append(("Password: ", PAM.PAM_PROMPT_ECHO_OFF))
        response_list = self._converse(query_list)

        auth_success = False
        if response_list is None:
            response_list = []
        for response, _ in response_list:
            if not len(response):
                continue
            password = response
            if password == entry['password']:
                query_list = [
                    ("Login successful", PAM.PAM_SUCCESS)
                    ]
                self._converse(query_list)
                auth_success = True

        if not auth_success:
            raise PAM.error(PAM.PAM_AUTH_ERR, "Authentication failed")

    def acct_mgmt(self): pass

class Stub_PamModule(object):
    """ Stub class for PAM authentication module """

    error = Stub_PamError

    [
        PAM_USER,
        PAM_CONV,
        PAM_SILENT,
        PAM_DISALLOW_NULL_AUTHTOK,
        PAM_PROMPT_ECHO_ON,
        PAM_PROMPT_ECHO_OFF,
        PAM_ERROR_MSG,
        PAM_TEXT_INFO,

        PAM_ABORT,
        PAM_AUTH_ERR,
        PAM_CRED_INSUFFICIENT,
        PAM_AUTHINFO_UNVAIL,
        PAM_MAXTRIES,
        PAM_USER_UNKNOWN,

        PAM_SUCCESS,
        ] = range(15)

    def pam(self):
        """ Return a handle to the authentication system """
        authenticator = Stub_PamAuth()
        return authenticator

stub_pam_module = Stub_PamModule()
PAM = stub_pam_module

class PamAuthService_TestCase(scaffold.TestCase):
    """ Test cases for PamAuthService class. """

    def setUp(self):
        """ Set up test fixtures """

        self.service_class = authservice.PamAuthService

        self.pwd_module_prev = authservice.pwd
        self.pam_module_prev = authservice.PAM
        self.pwd_module = Stub_PwdModule()
        self.pam_module = stub_pam_module
        authservice.pwd = self.pwd_module
        authservice.PAM = self.pam_module

    def tearDown(self):
        """ Tear down test fixtures """
        authservice.pwd = self.pwd_module_prev
        authservice.PAM = self.pam_module_prev

    def test_instantiate(self):
        """ New PosixAuthService instance should be created """
        instance = self.service_class()
        self.failIfIs(instance, None)

    def test_authenticate_unknown_user_raises_autherror(self):
        """ Auth for unknown user should raise AuthenticationError """
        instance = self.service_class()
        credentials = dict(
            username = "bogus",
            password = "bogus",
            )
        self.failUnlessRaises(
            authservice.AuthenticationError,
            instance.authenticate, credentials
            )

    def test_authenticate_bad_creds_returns_false(self):
        """ Auth for bad credentials should raise AuthenticationError """
        instance = self.service_class()
        credentials = dict(
            username = "fred",
            password = "bogus",
            )
        self.failUnlessRaises(
            authservice.AuthenticationError,
            instance.authenticate, credentials
            )

    def test_authenticate_good_creds_returns_username(self):
        """ Authentication for good credentials should return username """
        instance = self.service_class()
        credentials = dict(
            username = "fred",
            password = "password1",
            )
        username = credentials['username']
        result = instance.authenticate(credentials)
        self.failUnlessEqual(username, result)