Changeset 67

Show
Ignore:
Timestamp:
04/18/07 15:46:07
Author:
thejimmyg
Message:

Added a database example, changed permissions to work with the new user API and fixed some intercept problems with form, digest and basic

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • AuthKit/branches/0.4/README.txt

    r59 r67  
    33 
    44WARNING: THIS CODE IS NOT PRODUCTION READY 
     5 
     6Need Testing 
     7============ 
     8 
     9* Intercept code with redirect, passurl and forward 
    510 
    611Changes 
  • AuthKit/branches/0.4/authkit/authenticate/__init__.py

    r66 r67  
    229229    how to create your own ``Users`` objects. 
    230230    """ 
    231      
    232231    if not environ.has_key('authkit.users'): 
    233232        raise no_authkit_users_in_environ 
    234233    users = environ['authkit.users'] 
    235     if users.passwords.has_key(username.lower()) and users.passwords[username.lower()] == password: 
     234    if not users.user_exists(username): 
     235        return False 
     236    elif users.user_has_password(username.lower(), password): 
    236237        return True 
    237238    return False 
     
    255256    Only required if you intend to use HTTP digest authentication. 
    256257    """ 
    257     #import authkit.authenticate.digest 
    258258    if not environ.has_key('authkit.users'): 
    259259        raise no_authkit_users_in_environ 
    260260    users = environ['authkit.users'] 
    261     if users.passwords.has_key(username.lower()): 
    262         password = users.passwords[username.lower()
     261    if users.user_exists(username): 
     262        password = users.user(username)['password'
    263263        return digest.digest_password(realm, username, password) 
    264264    # After speaking to Clark Evans who wrote the origianl code, this is the correct thing: 
    265265    return None 
    266266 
    267 #def load_cookie_middleware(app, final, prefix): 
    268 #    """ 
    269 #    Various AuthKit authenticate mathods such as ``form`` and ``passurl`` use a 
    270 #    cookie. Their cookie support is based on the 
    271 #    ``authkit.authenticate.auth_tkt`` middleware.  This utility function reads 
    272 #    the cookie config options and returns the correctly configured cookie 
    273 #    middleware automatically, reducing duplication in the code. 
    274 #    """ 
    275 #    from paste.deploy.converters import asbool 
    276 #    from authkit.authenticate.auth_tkt import AuthKitCookieMiddleware 
    277 #    params = { 
    278 #        'secret':_get_value(final, 'cookie_secret', prefix),  
    279 #    } 
    280 #    for param in [ 
    281 #        ['cookie_name','cookie_name'],  
    282 #        ['cookie_includeip', 'include_ip'], 
    283 #        ['cookie_signout', 'logout_path'], 
    284 #        ['cookie_enforce', 'cookie_enforce'], 
    285 #    ]: 
    286 #        if param[0] in final: 
    287 #            params[param[1]] = final[param[0]] 
    288 #    cookie_params = {} 
    289 #    if final.has_key('cookie_params'): 
    290 #        data = _get_value(final, 'cookie_params', prefix) 
    291 #        if isinstance(data, str): 
    292 #            lines = data.replace('\r\n','\n').replace('\r','\n').split('\n') 
    293 #            for line in lines: 
    294 #                if line.strip(): 
    295 #                    parts = line.strip().split(':') 
    296 #                    name = parts[0].strip().lower() 
    297 #                    if name in [ 
    298 #                        'expires',   
    299 #                        'path',  
    300 #                        'comment',  
    301 #                        'domain', 
    302 #                        'max-age', 
    303 #                        'secure', 
    304 #                        'version', 
    305 #                    ]: 
    306 #                        cookie_params[name] = ':'.join(parts[1:]).strip() 
    307 #                    else: 
    308 #                        raise AuthKitConfigError('Invalid cookie parameter %r'%name) 
    309 #        elif isinstance(data, dict): 
    310 #            cookie_params = data.copy() 
    311 #        else: 
    312 #            raise AuthKitConfigError("Expected a string or dictionary for authkit.cookie_params") 
    313 #   # raise Exception(params) 
    314 #    if params.has_key('include_ip'): 
    315 #        params['include_ip'] = asbool(params['include_ip']) 
    316 #    app = AuthKitCookieMiddleware(app, cookie_params=cookie_params, **params) 
    317 #    return app 
    318  
    319267def get_authenticate_function(app, authenticate_conf, format, prefix): 
    320268    """ 
     
    324272    """ 
    325273    function = None 
     274    users = None 
    326275    if len(authenticate_conf) < 1: 
    327276        raise AuthKitConfigError('Expected at least one authenticate key, not %r'%authenticate_conf) 
     
    349298            else: 
    350299                raise Exception('Invalid format for authenticate function %r'%format) 
    351     return app, function 
     300    return app, function, users 
    352301 
    353302def get_template(template_conf, prefix): 
  • AuthKit/branches/0.4/authkit/authenticate/auth_tkt.py

    r61 r67  
    322322                return [msg] 
    323323            try: 
    324                 log.error("Parsing ticket secret %r, cookie value %r, remote address %s", self.secret, cookie_value, remote_addr) 
    325  
     324                log.debug("Parsing ticket secret %r, cookie value %r, remote address %s", self.secret, cookie_value, remote_addr) 
    326325                timestamp, userid, tokens, user_data = parse_ticket( 
    327326                    self.secret, cookie_value, remote_addr) 
  • AuthKit/branches/0.4/authkit/authenticate/basic.py

    r59 r67  
    44This implementation is identical to the `paste.auth.basic 
    55<http://pythonpaste.org/module-paste.auth.basic.html>`_ implemenation. 
     6 
     7 
     8Note:: If users are prompted to sign in this also seems to have the effect of 
     9    signing them out. 
     10 
    611""" 
    712#from paste.auth.basic import middleware 
     
    9398 
    9499    def __call__(self, environ, start_response): 
    95         result = self.authenticate(environ) 
    96         return result.wsgi_application(environ, start_response) 
     100        if environ.has_key('authkit.multi'): 
     101            # Shouldn't ever allow a response if this is called via the multi handler 
     102            authenitcation = self.authenticate.build_authentication() 
     103            return authenitcation.wsgi_application(environ, start_response) 
     104        else: 
     105            result = self.authenticate(environ) 
     106            return result.wsgi_application(environ, start_response) 
    97107 
    98108middleware = AuthBasicHandler 
    99109 
    100110class TryToAddUsername: 
    101     def __init__(self, application, realm, authfunc): 
     111    def __init__(self, application, realm, authfunc, users): 
    102112        self.application = application 
     113        self.users = users 
    103114        self.authenticate = AuthBasicAuthenticator(realm, authfunc) 
    104115 
    105116    def __call__(self, environ, start_response): 
     117        environ['authkit.users'] = self.users 
    106118        result = self.authenticate(environ) 
    107119        if isinstance(result, str): 
     
    121133): 
    122134    authenticate_conf = strip_base(auth_conf, 'authenticate.') 
    123     app, authfunc = get_authenticate_function( 
     135    app, authfunc, users = get_authenticate_function( 
    124136        app,  
    125137        authenticate_conf,  
     
    132144    app.add_method('basic', middleware, auth_conf['realm'], authfunc) 
    133145    app.add_checker('basic', status_checker) 
    134     app = TryToAddUsername(app, auth_conf['realm'], authfunc
     146    app = TryToAddUsername(app, auth_conf['realm'], authfunc, users
    135147    return app 
    136148 
  • AuthKit/branches/0.4/authkit/authenticate/digest.py

    r59 r67  
    44This implementation is identical to the `paste.auth.digest 
    55<http://pythonpaste.org/module-paste.auth.digest.html>`_ implemenation. 
     6 
     7Note:: If users are prompted to sign in this also seems to have the effect of 
     8    signing them out. 
    69""" 
    710 
     
    178181         
    179182    def __call__(self, environ, start_response): 
    180         method = REQUEST_METHOD(environ) 
    181         fullpath = SCRIPT_NAME(environ) + PATH_INFO(environ) 
    182         authorization = AUTHORIZATION(environ) 
    183         result = self.authenticate(environ, authorization, fullpath, method) 
    184         return result.wsgi_application(environ, start_response) 
     183        if environ.has_key('authkit.multi'): 
     184            # Shouldn't ever allow a response if this is called via the multi handler 
     185            authenitcation = self.authenticate.build_authentication() 
     186            return authenitcation.wsgi_application(environ, start_response) 
     187        else: 
     188            method = REQUEST_METHOD(environ) 
     189            fullpath = SCRIPT_NAME(environ) + PATH_INFO(environ) 
     190            authorization = AUTHORIZATION(environ) 
     191            result = self.authenticate(environ, authorization, fullpath, method) 
     192            return result.wsgi_application(environ, start_response) 
    185193         
    186194class TryToAddUsername: 
    187     def __init__(self, application, realm, authfunc): 
     195    def __init__(self, application, realm, authfunc, users): 
    188196        self.application = application 
     197        self.users = users 
    189198        self.authenticate = AuthDigestAuthenticator(realm, authfunc) 
    190199 
    191200    def __call__(self, environ, start_response): 
     201        environ['authkit.users'] = self.users 
    192202        method = REQUEST_METHOD(environ) 
    193203        fullpath = SCRIPT_NAME(environ) + PATH_INFO(environ) 
     
    213223): 
    214224    authenticate_conf = strip_base(auth_conf, 'authenticate.') 
    215     app, authfunc = get_authenticate_function( 
     225    app, authfunc, users = get_authenticate_function( 
    216226        app,  
    217227        authenticate_conf,  
     
    224234    app.add_method('digest', middleware, auth_conf['realm'], authfunc) 
    225235    app.add_checker('digest', status_checker) 
    226     app = TryToAddUsername(app, auth_conf['realm'], authfunc
     236    app = TryToAddUsername(app, auth_conf['realm'], authfunc, users
    227237    return app 
    228238 
  • AuthKit/branches/0.4/authkit/authenticate/form.py

    r61 r67  
    4343         
    4444    def __call__(self, environ, start_response): 
     45        # Shouldn't ever allow a response if this is called via the multi handler 
    4546        username = environ.get('REMOTE_USER','') 
    46         if username: 
    47             return self.application(environ, start_response) 
    48  
    4947        if 'POST' == environ['REQUEST_METHOD']: 
    5048            formvars = parse_formvars(environ, include_get_vars=False) 
     
    8583        template_ = template 
    8684    authenticate_conf = strip_base(auth_conf, 'authenticate.') 
    87     app, authfunc = get_authenticate_function( 
     85    app, authfunc, users = get_authenticate_function( 
    8886        app,  
    8987        authenticate_conf,  
  • AuthKit/branches/0.4/authkit/authenticate/multi.py

    r59 r67  
    55 
    66from paste.auth import multi 
     7import logging 
     8 
     9log = logging.getLogger('authkit.authenticate.multi') 
    710 
    811class NoBindingFoundError(Exception): 
     
    3033                status_.append(status) 
    3134                headers_.append(headers) 
    32                 exc_info_.append(exc_info)             
     35                exc_info_.append(exc_info) 
     36                log.debug("Status: %r, Headers: %r", status, headers) 
    3337            return self.default(environ, find) 
    3438 
     39        def logging_start_response(status, headers, exc_info=None): 
     40            log.debug("Matched binding returns status: %r, headers: %r, exc_info: %r", status, headers, exc_info) 
     41            return start_response(status, headers, exc_info) 
     42             
    3543        def check(): 
    3644            for (checker,binding) in self.predicate: 
    3745                if checker(environ): 
    38                     return binding(environ, start_response) 
     46                    log.debug("MultMiddleware self.predicate check() returning %r", binding) 
     47                    environ['authkit.multi'] = True 
     48                    return binding(environ, logging_start_response) 
    3949            for (checker,binding) in self.checker: 
    4050                if not len(status_): 
     
    4353                    raise Exception('No headers were returned by the application') 
    4454                if checker(environ, status_[0], headers_ and headers_[0] or []): 
    45                     return binding(environ, start_response) 
     55                    log.debug("MultMiddleware self.checker check() returning %r", binding) 
     56                    environ['authkit.multi'] = True 
     57                    return binding(environ, logging_start_response) 
    4658            raise NoBindingFoundError('Check failed') 
    47          
     59             
    4860        checked = False 
    4961        f = [] 
     
    5870                        result = check() 
    5971                    except NoBindingFoundError, e: 
     72                        log.debug("MutliMiddleware: No binding was found for the check") 
    6073                        start_response(status_[0], headers_ and headers_[0] or [], exc_info_[0]) 
    6174                    else: 
     75                        # Commented out because it could create huge logs very quickly! 
     76                        log.debug("Binding matched, returning result %r", result) 
    6277                        return result 
    6378                f.append(data) 
     
    6580            app_iter.close() 
    6681        return f                     
    67 # 
    68 #class ChangeTo401: 
    69 #    def __init__(self, app, catch=[], exclude=[]): 
    70 #        self.app = app 
    71 #        ex = [] 
    72 #        for e in exclude: 
    73 #            e_ = str(e).strip() 
    74 #            if str(e_) in ['401','*']: 
    75 #                raise AuthKitConfigError('You cannot exclude %s since this would disable the authkit middleware'%e_) 
    76 #            ex.append(e_) 
    77 #        self.catch = [] 
    78 #        for c in catch: 
    79 #            ch = str(c).strip() 
    80 #            if ch not in ex: 
    81 #                self.catch.append(ch) 
    82 #         
    83 #    def __call__(self, environ, start_response): 
    84 #        def authkit_start_response(status, headers, exc_info=None): 
    85 #            if '*' in self.catch: 
    86 #                status = '401 Please sign in' 
    87 #            else: 
    88 #                for code in self.catch: 
    89 #                    if str(code) == status[:3]: 
    90 #                        status = '401 Please sign in' 
    91 #            return start_response(status, headers, exc_info) 
    92 #        return self.app(environ, authkit_start_response) 
    9382 
    9483def status_checker(environ, status, headers): 
    95     if status[:3] in environ['authkit.intercept']: 
     84    log.debug("Status checker recieved status %r, headers %r, intecept %r", status, headers, environ['authkit.intercept']) 
     85    if str(status[:3]) in environ['authkit.intercept']: 
     86        log.debug("Status checker returns True") 
    9687        return True 
     88    log.debug("Status checker returns False") 
    9789    return False 
  • AuthKit/branches/0.4/authkit/authenticate/passurl.py

    r59 r67  
    121121        raise Exception("Invalid store type %r"%store) 
    122122    return conn, cstore 
    123      
    124123     
    125124class AuthOpenIDHandler: 
     
    176175 
    177176    def verify(self, environ, start_response): 
    178        # XXX This method should accept sreg options and continue with them 
     177        # XXX This method should accept sreg options and continue with them 
    179178        baseurl = self.baseurl or construct_url(environ, with_query_string=False, with_path_info=False) 
    180179        params = dict(paste.request.parse_formvars(environ)) 
  • AuthKit/branches/0.4/authkit/permissions.py

    r61 r67  
    5252   """ 
    5353 
    54     def check(app, environ, start_response):  
     54    def check(self, app, environ, start_response):  
    5555        return app(environ, start_response) 
    5656 
     
    189189        In this implementation role names are case insensitive. 
    190190        """ 
     191        if not environ.has_key('authkit.users'): 
     192            raise no_authkit_users_in_environ 
    191193        if not environ.has_key('REMOTE_USER'): 
    192194            if self.error:  
    193195                raise self.error 
    194196            raise NotAuthenticatedError('Not authenticated') 
    195              
    196         if not environ.has_key('authkit.users'): 
    197             raise no_authkit_users_in_environ 
    198197        users = environ['authkit.users'] 
    199          
    200         if not users.passwords.has_key(environ['REMOTE_USER']): 
     198        if not users.user_exists(environ['REMOTE_USER']): 
    201199            raise NotAuthorizedError('No such user') 
    202  
    203         if not users.roles.has_key(environ['REMOTE_USER']): 
    204             if self.roles == None: 
    205                 return app(environ, start_response) 
    206             else: 
    207                 raise NotAuthorizedError("User has no roles specified") 
    208          
    209         if not self.all: 
     200        # Check the groups specified when setup actually exist 
     201        for role in self.roles: 
     202            if not users.role_exists(role): 
     203                raise Exception("No such role %r exists"%role) 
     204        if self.all: 
    210205            for role in self.roles: 
    211                 if role.lower() in users.roles[environ['REMOTE_USER']]: 
     206                if not users.user_has_role(environ['REMOTE_USER'], role): 
     207                    if self.error: 
     208                        raise self.error 
     209                    else: 
     210                        raise NotAuthorizedError("User doesn't have the role %s"%role.lower()) 
     211            return app(environ, start_response) 
     212        else: 
     213            for role in self.roles: 
     214                if users.user_has_role(environ['REMOTE_USER'], role): 
    212215                    return app(environ, start_response) 
    213216            if self.error: 
     
    215218            else: 
    216219                raise NotAuthorizedError("User doesn't have any of the specified roles") 
    217         else: 
    218             for role in self.roles: 
    219                 if role.lower() not in users.roles[environ['REMOTE_USER']]: 
    220                     if self.error: 
    221                         raise self.error 
    222                     else: 
    223                         raise NotAuthorizedError("User doesn't have the role %s"%role.lower()) 
    224             return app(environ, start_response) 
    225  
    226220     
    227221class HasAuthKitGroup(RequestPermission): 
     
    246240        In this implementation group names are case insensitive. 
    247241        """ 
     242        if not environ.has_key('authkit.users'): 
     243            raise no_authkit_users_in_environ 
    248244        if not environ.has_key('REMOTE_USER'): 
    249245            if self.error:  
    250246                raise self.error 
    251247            raise NotAuthenticatedError('Not authenticated') 
    252              
    253         if not environ.has_key('authkit.users'): 
    254             raise no_authkit_users_in_environ 
    255248        users = environ['authkit.users'] 
    256         if not users.passwords.has_key(environ['REMOTE_USER']): 
     249        # Check the groups specified when setup actually exist 
     250        for group in self.groups: 
     251            if group is not None: 
     252                if not users.group_exists(group): 
     253                    raise Exception("No such group %r exists"%group) 
     254         
     255        if not users.user_exists(environ['REMOTE_USER']): 
    257256            raise NotAuthorizedError('No such user') 
    258         if not users.groups.has_key(environ['REMOTE_USER']) and self.groups == None: 
    259             return app(environ, start_response) 
    260         elif self.groups == None: 
    261             if self.error: 
    262                 raise self.error 
    263             else: 
    264                 raise NotAuthorizedError("User is not a member of any group") 
    265257        for group in self.groups: 
    266             if users.groups[environ['REMOTE_USER']] == group.lower(): 
     258            if users.user_has_group(environ['REMOTE_USER'], group): 
    267259                return app(environ, start_response) 
    268260        if self.error: 
     
    270262        else: 
    271263            raise NotAuthorizedError("User is not a member of the specified group(s) %r"%self.groups) 
    272      
     264 
    273265class ValidAuthKitUser(UserIn): 
    274266    """ 
     
    279271        pass 
    280272     
    281     def check(environ, start_response): 
     273    def check(self, app, environ, start_response): 
    282274        if not environ.has_key('authkit.users'): 
    283275            raise no_authkit_users_in_environ 
    284         self.users = environ['authkit.users'].usernames 
    285         return UserIn.check(self, environ, start_response) 
    286  
     276        if not environ.has_key('REMOTE_USER'): 
     277            raise NotAuthenticatedError('Not Authenticated') 
     278        if not environ['authkit.users'].user_exists(environ['REMOTE_USER']): 
     279            raise NotAuthorizedError('You are not one of the users allowed to access this resource.') 
     280        return app(environ, start_response) 
  • AuthKit/branches/0.4/test/test.py

    r66 r67  
    5252        assert 'You Have Access To This Page.' in res 
    5353 
     54def test_intercept(): 
     55    # XXX Note, these tests don't test when the inclusion of a username and only test form 
     56    # should also test all the other methods too for correct behaviour 
     57    def sample_app(environ, start_response): 
     58        if environ.get('PATH_INFO') == '/403': 
     59            start_response('403 Forbidden', [('Content-type', 'text/plain')]) 
     60            return ['Access denied'] 
     61        elif environ.get('PATH_INFO') == '/401': 
     62            start_response('401 Unauth', [('Content-type', 'text/plain')]) 
     63            return ['Not Authed'] 
     64        elif environ.get('PATH_INFO') == '/702': 
     65            start_response('702 Doesnt exist', [('Content-type', 'text/plain')]) 
     66            return ['Access denied'] 
     67        elif environ.get('PATH_INFO') == '/500': 
     68            start_response('500 Error', [('Content-type', 'text/plain')]) 
     69            return ['Error'] 
     70 
     71    app = middleware( 
     72        sample_app, 
     73        setup_method='form,cookie', 
     74        cookie_secret='secret encryption string', 
     75        form_authenticate_user_data = """ 
     76            Username1:password1 
     77            username2:password2 
     78        """, 
     79        cookie_signoutpath = '/signout', 
     80        setup_intercept = "403, 702", 
     81    ) 
     82    res = TestApp(app).get('/403', status=401) 
     83    assertEqual(res.header('content-type'), 'text/html') 
     84    # XXX Should this keep the original status code or not? 
     85    assertEqual(res.full_status, '401 Unauthorized') 
     86    assert 'Please Sign In' in res 
     87 
     88    res = TestApp(app).get('/702', status=401) 
     89    assertEqual(res.header('content-type'), 'text/html') 
     90    # XXX Should this keep the original status code or not? 
     91    assertEqual(res.full_status, '401 Unauthorized') 
     92    assert 'Please Sign In' in res 
     93 
     94    res = TestApp(app).get('/500', status=500) 
     95    assertEqual(res.header('content-type'), 'text/plain') 
     96    assertEqual(res.full_status, '500 Error') 
     97    assert 'Error' in res 
     98     
     99    res = TestApp(app).get('/401', status=401) 
     100    assertEqual(res.header('content-type'), 'text/plain') 
     101    assertEqual(res.full_status, '401 Unauth') 
     102    assert 'Not Authed' in res 
     103     
    54104def test_fail(): 
    55105    for app in [basic_app, digest_app]: 
     
    90140        os.remove("test/mydb.db") 
    91141    import test_model 
    92     from authkit.users import UsersFromDatabase 
     142    from authkit.users.database import UsersFromDatabase 
    93143     
    94144