repoze.what-1.0.9/0000755000175000017500000000000011356577163013747 5ustar gustavogustavorepoze.what-1.0.9/tests/0000755000175000017500000000000011356575761015113 5ustar gustavogustavorepoze.what-1.0.9/tests/__init__.py0000644000175000017500000000151711356575761017230 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """Test suite for repoze.what.""" repoze.what-1.0.9/tests/test_middleware.py0000644000175000017500000003436311356575761020652 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Tests for the repoze.what middleware. """ import unittest, os, logging from zope.interface.verify import verifyClass from repoze.who.middleware import PluggableAuthenticationMiddleware from repoze.who.classifiers import default_challenge_decider, \ default_request_classifier from repoze.who.interfaces import IAuthenticator, IMetadataProvider from repoze.who.plugins.form import RedirectingFormPlugin from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin from repoze.who.plugins.basicauth import BasicAuthPlugin from repoze.who.plugins.testutil import AuthenticationForgerPlugin, \ AuthenticationForgerMiddleware from repoze.what.middleware import AuthorizationMetadata, setup_auth from base import FakeAuthenticator, FakeGroupSourceAdapter, \ FakePermissionSourceAdapter, FakeLogger #{ Fake adapters/plugins class FakeGroupFetcher1(object): def find_sections(self, credentials): return ('directors', 'sysadmins') class FakeGroupFetcher2(object): def find_sections(self, credentials): return ('webdesigners', 'directors') class FakeGroupFetcher3(object): def find_sections(self, credentials): return ('graphic-designers', 'sysadmins') class FakePermissionFetcher1(object): def find_sections(self, group): if group == 'sysadmins': return ('view-users', 'edit-users', 'add-users') else: return ('view-users', ) class FakePermissionFetcher2(object): def find_sections(self, group): if group == 'graphic-designers': return ('upload-images', ) elif group == 'directors': return ('hire', 'fire') else: return tuple() class FakePermissionFetcher3(object): def find_sections(self, group): return ('contact', ) #{ The tests themselves class TestAuthorizationMetadata(unittest.TestCase): """Tests for the ``AuthorizationMetadata`` IMetadata plugin. All of these tests, except the first one, check the behavior of the plugin with random groups and permission fetchers. """ def _check_groups_and_permissions(self, environ, identity, expected_groups, expected_permissions): # Using sets to forget about order: self.assertEqual(set(identity['groups']), set(expected_groups)) self.assertEqual(set(identity['permissions']), set(expected_permissions)) # Ensure the repoze.what.credentials environ key is set: assert 'repoze.what.credentials' in environ, \ 'The repoze.what credentials were not set' credentials = environ['repoze.what.credentials'] self.assertEqual(set(credentials['groups']), set(expected_groups)) self.assertEqual(set(credentials['permissions']), set(expected_permissions)) def test_implements(self): verifyClass(IMetadataProvider, AuthorizationMetadata, tentative=True) def test_adapters_are_loaded_into_environ(self): """The available adapters must be loaded into the WSGI environ""" environ = {} identity = {'repoze.who.userid': 'someone'} group_adapters = { 'tech-team': FakeGroupFetcher1(), 'executive': FakeGroupFetcher3() } permission_adapters = {'perms1': FakePermissionFetcher2()} plugin = AuthorizationMetadata(group_adapters, permission_adapters) plugin.add_metadata(environ, identity) # Testing it: adapters = { 'groups': group_adapters, 'permissions': permission_adapters } self.assertEqual(adapters, environ.get('repoze.what.adapters')) def test_userid_in_credentials(self): """The userid must also be set in the credentials dict""" # For repoze.what 1.X, it's just copied from the repoze.who credentials: environ = {} identity = {'repoze.who.userid': 'someone'} expected_credentials = { 'repoze.what.userid': 'someone', 'groups': (), 'permissions': () } plugin = AuthorizationMetadata() plugin.add_metadata(environ, identity) self.assertEqual(environ['repoze.what.credentials'], expected_credentials) def test_no_groups_and_permissions(self): """Groups/permissions-based authorization is optional""" environ = {} identity = {'repoze.who.userid': 'whatever'} # Configuring the plugin: group_adapters = {'executive': FakeGroupFetcher3()} permission_adapters = {'perms1': FakePermissionFetcher2()} plugin = AuthorizationMetadata() plugin.add_metadata(environ, identity) # Testing it: self._check_groups_and_permissions(environ, identity, (), ()) def test_logger(self): # Setting up logging: logger = FakeLogger() environ = {'repoze.who.logger': logger} identity = {'repoze.who.userid': 'whatever'} # Configuring the plugin: group_adapters = {'executive': FakeGroupFetcher3()} permission_adapters = {'perms1': FakePermissionFetcher2()} plugin = AuthorizationMetadata(group_adapters, permission_adapters) plugin.add_metadata(environ, identity) # Testing it: messages = "; ".join(logger.messages['info']) assert "graphic-designers" in messages assert "upload-images" in messages def test_add_metadata1(self): identity = {'repoze.who.userid': 'whatever'} environ = {} group_adapters = { 'tech-team': FakeGroupFetcher1(), 'executive': FakeGroupFetcher3() } permission_adapters = {'perms1': FakePermissionFetcher2()} plugin = AuthorizationMetadata(group_adapters, permission_adapters) plugin.add_metadata(environ, identity) expected_groups = ('directors', 'sysadmins', 'graphic-designers') expected_permissions = ('hire', 'fire', 'upload-images') self._check_groups_and_permissions(environ, identity, expected_groups, expected_permissions) def test_add_metadata2(self): identity = {'repoze.who.userid': 'whatever'} environ = {} group_adapters = {'a_nice_group': FakeGroupFetcher2()} permission_adapters = {'global_perms': FakePermissionFetcher3()} plugin = AuthorizationMetadata(group_adapters, permission_adapters) plugin.add_metadata(environ, identity) expected_groups = ('webdesigners', 'directors') expected_permissions = ('contact', ) self._check_groups_and_permissions(environ, identity, expected_groups, expected_permissions) def test_add_metadata3(self): identity = {'repoze.who.userid': 'whatever'} environ = {} group_adapters = { 'tech-team1': FakeGroupFetcher1(), 'tech-team2': FakeGroupFetcher2(), 'executive-team': FakeGroupFetcher3() } permission_adapters = { 'human-resources': FakePermissionFetcher1(), 'website-management': FakePermissionFetcher2(), 'gallery-administration': FakePermissionFetcher3() } plugin = AuthorizationMetadata(group_adapters, permission_adapters) plugin.add_metadata(environ, identity) expected_groups = ('graphic-designers', 'sysadmins', 'webdesigners', 'directors') expected_permissions = ('view-users', 'edit-users', 'add-users', 'hire', 'fire', 'upload-images', 'contact') self._check_groups_and_permissions(environ, identity, expected_groups, expected_permissions) def test_add_metadata4(self): identity = {'repoze.who.userid': 'whatever'} environ = {} group_adapters = { 'group1': FakeGroupFetcher1(), 'group2': FakeGroupFetcher2(), 'group3': FakeGroupFetcher3() } permission_adapters = {'my_perms': FakePermissionFetcher3()} plugin = AuthorizationMetadata(group_adapters, permission_adapters) plugin.add_metadata(environ, identity) expected_groups = ('graphic-designers', 'sysadmins', 'webdesigners', 'directors') expected_permissions = ('contact', ) self._check_groups_and_permissions(environ, identity, expected_groups, expected_permissions) def test_add_metadata5(self): identity = {'repoze.who.userid': 'rms'} environ = {} group_adapters = {'my_group': FakeGroupSourceAdapter()} permission_adapters = {'my_perm': FakePermissionSourceAdapter()} plugin = AuthorizationMetadata(group_adapters, permission_adapters) plugin.add_metadata(environ, identity) expected_groups = ('admins', 'developers') expected_permissions = ('edit-site', 'commit') self._check_groups_and_permissions(environ, identity, expected_groups, expected_permissions) class TestSetupAuth(unittest.TestCase): """Tests for the setup_auth() function""" def setUp(self): self.old_auth_log = os.environ.get('AUTH_LOG') os.environ['AUTH_LOG'] = '0' def tearDown(self): os.environ['AUTH_LOG'] = str(self.old_auth_log) def _in_registry(self, app, registry_key, registry_type): assert registry_key in app.name_registry, ('Key "%s" not in registry' % registry_key) assert isinstance(app.name_registry[registry_key], registry_type), \ 'Registry key "%s" is of type "%s" (not "%s")' % \ (registry_key, app.name_registry[registry_key].__class__.__name__, registry_type.__name__) def _makeEnviron(self, kw=None): environ = {} environ['wsgi.version'] = (1,0) if kw is not None: environ.update(kw) return environ def _makeApp(self, groups, permissions, **who_args): cookie = AuthTktCookiePlugin('secret', 'authtkt') form = RedirectingFormPlugin('/login', '/login_handler', '/logout_handler', rememberer_name='cookie') identifiers = [('main_identifier', form), ('cookie', cookie)] authenticators = [('fake_authenticator', FakeAuthenticator())] challengers = [('form', form)] if groups is None: app_with_auth = setup_auth( DummyApp(), identifiers=identifiers, authenticators=authenticators, challengers=challengers, **who_args ) else: app_with_auth = setup_auth( DummyApp(), groups, permissions, identifiers=identifiers, authenticators=authenticators, challengers=challengers, **who_args ) return app_with_auth def test_no_extras(self): groups = [FakeGroupSourceAdapter()] permissions = [FakePermissionSourceAdapter()] app = self._makeApp(groups, permissions) assert isinstance(app, PluggableAuthenticationMiddleware) self._in_registry(app, 'main_identifier', RedirectingFormPlugin) self._in_registry(app, 'authorization_md', AuthorizationMetadata) self._in_registry(app, 'cookie', AuthTktCookiePlugin) self._in_registry(app, 'fake_authenticator', FakeAuthenticator) self._in_registry(app, 'form', RedirectingFormPlugin) assert isinstance(app.challenge_decider, default_challenge_decider.__class__) assert isinstance(app.classifier, default_request_classifier.__class__) def test_with_auth_log(self): os.environ['AUTH_LOG'] = '1' groups = [FakeGroupSourceAdapter()] permissions = [FakePermissionSourceAdapter()] app = self._makeApp(groups, permissions) def test_no_groups_and_permissions(self): """Groups/permissions-based authorization must be optional""" groups = None permissions = None app = self._makeApp(groups, permissions) self._in_registry(app, 'authorization_md', AuthorizationMetadata) authorization_md = app.name_registry['authorization_md'] self.assertEqual(authorization_md.group_adapters, None) self.assertEqual(authorization_md.permission_adapters, None) def test_without_authentication(self): groups = [FakeGroupSourceAdapter()] permissions = [FakePermissionSourceAdapter()] app = self._makeApp(groups, permissions, skip_authentication=True) assert isinstance(app, AuthenticationForgerMiddleware) self._in_registry(app, 'auth_forger', AuthenticationForgerPlugin) self._in_registry(app, 'cookie', AuthTktCookiePlugin) assert isinstance(app.challenge_decider, default_challenge_decider.__class__) assert isinstance(app.classifier, default_request_classifier.__class__) class DummyApp: environ = None def __call__(self, environ, start_response): self.environ = environ return [] #} repoze.what-1.0.9/tests/test_predicates.py0000644000175000017500000004716211356575761020661 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Tests for the predicates. """ import unittest from repoze.what import predicates from tests.base import FakeLogger, encode_multipart_formdata class BasePredicateTester(unittest.TestCase): """Base test case for predicates.""" def eval_met_predicate(self, p, environ): """Evaluate a predicate that should be met""" self.assertEqual(p.check_authorization(environ), None) self.assertEqual(p.is_met(environ), True) def eval_unmet_predicate(self, p, environ, expected_error): """Evaluate a predicate that should not be met""" credentials = environ.get('repoze.what.credentials') # Testing check_authorization try: p.evaluate(environ, credentials) self.fail('Predicate must not be met; expected error: %s' % expected_error) except predicates.NotAuthorizedError, error: self.assertEqual(unicode(error), expected_error) # Testing is_met: self.assertEqual(p.is_met(environ), False) #{ The test suite itself class TestPredicate(BasePredicateTester): def test_evaluate_isnt_implemented(self): p = MockPredicate() self.failUnlessRaises(NotImplementedError, p.evaluate, None, None) def test_message_is_changeable(self): previous_msg = EqualsTwo.message new_msg = 'It does not equal two!' p = EqualsTwo(msg=new_msg) self.assertEqual(new_msg, p.message) def test_message_isnt_changed_unless_required(self): previous_msg = EqualsTwo.message p = EqualsTwo() self.assertEqual(previous_msg, p.message) def test_unicode_messages(self): unicode_msg = u'请登陆' p = EqualsTwo(msg=unicode_msg) environ = {'test_number': 3} self.eval_unmet_predicate(p, environ, unicode_msg) def test_authorized(self): logger = FakeLogger() environ = {'test_number': 4} environ['repoze.who.logger'] = logger p = EqualsFour() p.check_authorization(environ) info = logger.messages['info'] assert "Authorization granted" == info[0] def test_unauthorized(self): logger = FakeLogger() environ = {'test_number': 3} environ['repoze.who.logger'] = logger p = EqualsFour(msg="Go away!") try: p.check_authorization(environ) self.fail('Authorization must have been rejected') except predicates.NotAuthorizedError, e: self.assertEqual(str(e), "Go away!") # Testing the logs: info = logger.messages['info'] assert "Authorization denied: Go away!" == info[0] def test_unauthorized_with_unicode_message(self): # This test is broken on Python 2.4 and 2.5 because the unicode() # function doesn't work when converting an exception into an unicode # string (this is, to extract its message). unicode_msg = u'请登陆' logger = FakeLogger() environ = {'test_number': 3} environ['repoze.who.logger'] = logger p = EqualsFour(msg=unicode_msg) try: p.check_authorization(environ) self.fail('Authorization must have been rejected') except predicates.NotAuthorizedError, e: self.assertEqual(unicode(e), unicode_msg) # Testing the logs: info = logger.messages['info'] assert "Authorization denied: %s" % unicode_msg == info[0] def test_custom_failure_message(self): message = u'This is a custom message whose id is: %(id_number)s' id_number = 23 p = EqualsFour(msg=message) try: p.unmet(message, id_number=id_number) self.fail('An exception must have been raised') except predicates.NotAuthorizedError, e: self.assertEqual(unicode(e), message % dict(id_number=id_number)) def test_getting_variables(self): """ The Predicate.parse_variables() method must return POST and GET variables. """ # -- Setting the environ up from StringIO import StringIO post_vars = [('postvar1', 'valA')] content_type, body = encode_multipart_formdata(post_vars) environ = { 'QUERY_STRING': 'getvar1=val1&getvar2=val2', 'REQUEST_METHOD':'POST', 'wsgi.input': StringIO(body), 'CONTENT_TYPE': content_type, 'CONTENT_LENGTH': len(body)} # -- Testing it p = EqualsFour() expected_variables = { 'get': {'getvar1': 'val1', 'getvar2': 'val2'}, 'post': {'postvar1': 'valA'}, 'positional_args': (), 'named_args': {}, } self.assertEqual(p.parse_variables(environ), expected_variables) def test_getting_variables_with_routing_args(self): """ The Predicate.parse_variables() method must return wsgiorg.routing_args arguments too. """ # -- Setting the environ up positional_args = (45, 'www.example.com', 'wait@busstop.com') named_args = {'language': 'es'} environ = {'wsgiorg.routing_args': (positional_args, named_args)} # -- Testing it p = EqualsFour() expected_variables = { 'get': {}, 'post': {}, 'positional_args': positional_args, 'named_args': named_args, } self.assertEqual(p.parse_variables(environ), expected_variables) def test_credentials_dict_when_anonymous(self): """The credentials must be a dict even if the user is anonymous""" class CredentialsPredicate(predicates.Predicate): message = "Some text" def evaluate(self, environ, credentials): if 'something' in credentials: self.unmet() # --- Setting the environ up environ = {} # --- Testing it: p = CredentialsPredicate() self.eval_met_predicate(p, environ) self.assertEqual(True, p.is_met(environ)) class TestDeprecatedPredicate(BasePredicateTester): """ Test that predicates using the deprecated ``_eval_with_environ()`` and ``evaluate()`` are still supported. """ def test_met_eval_with_environ(self): environ = {} p = DeprecatedEvalWithEnvironPredicate(True) self.eval_met_predicate(p, environ) def test_unmet_eval_with_environ(self): environ = {} error = 'This is a deprecated predicate' p = DeprecatedEvalWithEnvironPredicate(False) self.eval_unmet_predicate(p, environ, error) class TestCompoundPredicate(BasePredicateTester): def test_one_predicate_works(self): p = EqualsTwo() cp = predicates.CompoundPredicate(p) self.assertEqual(cp.predicates, (p,)) def test_two_predicates_work(self): p1 = EqualsTwo() p2 = MockPredicate() cp = predicates.CompoundPredicate(p1, p2) self.assertEqual(cp.predicates, (p1, p2)) class TestNotPredicate(BasePredicateTester): def test_failure(self): environ = {'test_number': 4} # It must NOT equal 4 p = predicates.Not(EqualsFour()) # It equals 4! self.eval_unmet_predicate(p, environ, 'The condition must not be met') def test_failure_with_custom_message(self): environ = {'test_number': 4} # It must not equal 4 p = predicates.Not(EqualsFour(), msg='It must not equal four') # It equals 4! self.eval_unmet_predicate(p, environ, 'It must not equal four') def test_success(self): environ = {'test_number': 5} # It must not equal 4 p = predicates.Not(EqualsFour()) # It doesn't equal 4! self.eval_met_predicate(p, environ) class TestAllPredicate(BasePredicateTester): def test_one_true(self): environ = {'test_number': 2} p = predicates.All(EqualsTwo()) self.eval_met_predicate(p, environ) def test_one_false(self): environ = {'test_number': 3} p = predicates.All(EqualsTwo()) self.eval_unmet_predicate(p, environ, "Number 3 doesn't equal 2") def test_two_true(self): environ = {'test_number': 4} p = predicates.All(EqualsFour(), GreaterThan(3)) self.eval_met_predicate(p, environ) def test_two_false(self): environ = {'test_number': 1} p = predicates.All(EqualsFour(), GreaterThan(3)) self.eval_unmet_predicate(p, environ, "Number 1 doesn't equal 4") def test_two_mixed(self): environ = {'test_number': 5} p = predicates.All(EqualsFour(), GreaterThan(3)) self.eval_unmet_predicate(p, environ, "Number 5 doesn't equal 4") class TestAnyPredicate(BasePredicateTester): def test_one_true(self): environ = {'test_number': 2} p = predicates.Any(EqualsTwo()) self.eval_met_predicate(p, environ) def test_one_false(self): environ = {'test_number': 3} p = predicates.Any(EqualsTwo()) self.eval_unmet_predicate(p, environ, "At least one of the following predicates must be " "met: Number 3 doesn't equal 2") def test_two_true(self): environ = {'test_number': 4} p = predicates.Any(EqualsFour(), GreaterThan(3)) self.eval_met_predicate(p, environ) def test_two_false(self): environ = {'test_number': 1} p = predicates.Any(EqualsFour(), GreaterThan(3)) self.eval_unmet_predicate(p, environ, "At least one of the following predicates must be " "met: Number 1 doesn't equal 4, 1 is not greater " "than 3") def test_two_mixed(self): environ = {'test_number': 5} p = predicates.Any(EqualsFour(), GreaterThan(3)) self.eval_met_predicate(p, environ) class TestIsUserPredicate(BasePredicateTester): def test_user_without_credentials(self): environ = {} p = predicates.is_user('gustavo') self.eval_unmet_predicate(p, environ, 'The current user must be "gustavo"') def test_user_without_userid(self): environ = {'repoze.what.credentials': {}} p = predicates.is_user('gustavo') self.eval_unmet_predicate(p, environ, 'The current user must be "gustavo"') def test_right_user(self): environ = make_environ('gustavo') p = predicates.is_user('gustavo') self.eval_met_predicate(p, environ) def test_wrong_user(self): environ = make_environ('andreina') p = predicates.is_user('gustavo') self.eval_unmet_predicate(p, environ, 'The current user must be "gustavo"') class TestInGroupPredicate(BasePredicateTester): def test_user_belongs_to_group(self): environ = make_environ('gustavo', ['developers']) p = predicates.in_group('developers') self.eval_met_predicate(p, environ) def test_user_doesnt_belong_to_group(self): environ = make_environ('gustavo', ['developers', 'admins']) p = predicates.in_group('designers') self.eval_unmet_predicate(p, environ, 'The current user must belong to the group "designers"') class TestInAllGroupsPredicate(BasePredicateTester): def test_user_belongs_to_groups(self): environ = make_environ('gustavo', ['developers', 'admins']) p = predicates.in_all_groups('developers', 'admins') self.eval_met_predicate(p, environ) def test_user_doesnt_belong_to_groups(self): environ = make_environ('gustavo', ['users', 'admins']) p = predicates.in_all_groups('developers', 'designers') self.eval_unmet_predicate(p, environ, 'The current user must belong to the group "developers"') def test_user_doesnt_belong_to_one_group(self): environ = make_environ('gustavo', ['developers']) p = predicates.in_all_groups('developers', 'designers') self.eval_unmet_predicate(p, environ, 'The current user must belong to the group "designers"') class TestInAnyGroupsPredicate(BasePredicateTester): def test_user_belongs_to_groups(self): environ = make_environ('gustavo', ['developers',' admins']) p = predicates.in_any_group('developers', 'admins') self.eval_met_predicate(p, environ) def test_user_doesnt_belong_to_groups(self): environ = make_environ('gustavo', ['users', 'admins']) p = predicates.in_any_group('developers', 'designers') self.eval_unmet_predicate(p, environ, 'The member must belong to at least one of the ' 'following groups: developers, designers') def test_user_doesnt_belong_to_one_group(self): environ = make_environ('gustavo', ['designers']) p = predicates.in_any_group('developers', 'designers') self.eval_met_predicate(p, environ) class TestIsAnonymousPredicate(BasePredicateTester): def test_authenticated_user(self): environ = make_environ('gustavo') p = predicates.is_anonymous() self.eval_unmet_predicate(p, environ, 'The current user must be anonymous') def test_anonymous_user(self): environ = {} p = predicates.is_anonymous() self.eval_met_predicate(p, environ) class TestNotAnonymousPredicate(BasePredicateTester): def test_authenticated_user(self): environ = make_environ('gustavo') p = predicates.not_anonymous() self.eval_met_predicate(p, environ) def test_anonymous_user(self): environ = {} p = predicates.not_anonymous() self.eval_unmet_predicate(p, environ, 'The current user must have been authenticated') class TestHasPermissionPredicate(BasePredicateTester): def test_user_has_permission(self): environ = make_environ('gustavo', permissions=['watch-tv']) p = predicates.has_permission('watch-tv') self.eval_met_predicate(p, environ) def test_user_doesnt_have_permission(self): environ = make_environ('gustavo', permissions=['watch-tv']) p = predicates.has_permission('eat') self.eval_unmet_predicate(p, environ, 'The user must have the "eat" permission') class TestHasAllPermissionsPredicate(BasePredicateTester): def test_user_has_all_permissions(self): environ = make_environ('gustavo', permissions=['watch-tv', 'party', 'eat']) p = predicates.has_all_permissions('watch-tv', 'eat') self.eval_met_predicate(p, environ) def test_user_doesnt_have_permissions(self): environ = make_environ('gustavo', permissions=['watch-tv', 'party', 'eat']) p = predicates.has_all_permissions('jump', 'scream') self.eval_unmet_predicate(p, environ, 'The user must have the "jump" permission') def test_user_has_one_permission(self): environ = make_environ('gustavo', permissions=['watch-tv', 'party', 'eat']) p = predicates.has_all_permissions('party', 'scream') self.eval_unmet_predicate(p, environ, 'The user must have the "scream" permission') class TestUserHasAnyPermissionsPredicate(BasePredicateTester): def test_user_has_all_permissions(self): environ = make_environ('gustavo', permissions=['watch-tv', 'party', 'eat']) p = predicates.has_any_permission('watch-tv', 'eat') self.eval_met_predicate(p, environ) def test_user_doesnt_have_all_permissions(self): environ = make_environ('gustavo', permissions=['watch-tv', 'party', 'eat']) p = predicates.has_any_permission('jump', 'scream') self.eval_unmet_predicate(p, environ, 'The user must have at least one of the following ' 'permissions: jump, scream') def test_user_has_one_permission(self): environ = make_environ('gustavo', permissions=['watch-tv', 'party', 'eat']) p = predicates.has_any_permission('party', 'scream') self.eval_met_predicate(p, environ) #{ Test utilities def make_environ(user, groups=None, permissions=None): """Make a WSGI enviroment with the credentials dict""" credentials = {'repoze.what.userid': user} credentials['groups'] = groups or [] credentials['permissions'] = permissions or [] environ = {'repoze.what.credentials': credentials} return environ #{ Mock definitions class MockPredicate(predicates.Predicate): message = "I'm a fake predicate" class EqualsTwo(predicates.Predicate): message = "Number %(number)s doesn't equal 2" def evaluate(self, environ, credentials): number = environ.get('test_number') if number != 2: self.unmet(number=number) class EqualsFour(predicates.Predicate): message = "Number %(number)s doesn't equal 4" def evaluate(self, environ, credentials): number = environ.get('test_number') if number == 4: return self.unmet(number=number) class GreaterThan(predicates.Predicate): message = "%(number)s is not greater than %(compared_number)s" def __init__(self, compared_number, **kwargs): super(GreaterThan, self).__init__(**kwargs) self.compared_number = compared_number def evaluate(self, environ, credentials): number = environ.get('test_number') if not number > self.compared_number: self.unmet(number=number, compared_number=self.compared_number) class LessThan(predicates.Predicate): message = "%(number)s must be less than %(compared_number)s" def __init__(self, compared_number, **kwargs): super(LessThan, self).__init__(**kwargs) self.compared_number = compared_number def evaluate(self, environ, credentials): number = environ.get('test_number') if not number < self.compared_number: self.unmet(number=number, compared_number=self.compared_number) class DeprecatedEvalWithEnvironPredicate(predicates.Predicate): message = "This is a deprecated predicate" def __init__(self, result, **kwargs): super(DeprecatedEvalWithEnvironPredicate, self).__init__(**kwargs) self.result = result def _eval_with_environ(self, environ): return self.result #} repoze.what-1.0.9/tests/test_release.py0000644000175000017500000000227511356575761020152 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2008-2009, Gustavo Narea # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Tests for the "release" module. """ import unittest, os from repoze.what import release _here = os.path.abspath(os.path.dirname(__file__)) _root = os.path.dirname(_here) version = open(os.path.join(_root, 'VERSION.txt')).readline().rstrip() class TestRelease(unittest.TestCase): def test_version(self): self.assertEqual(version, release.version) def test_major_version(self): # I prefer to update this on every major release -- Gustavo self.assertEqual(1, release.major_version) repoze.what-1.0.9/tests/test_authorize.py0000644000175000017500000000753611356575761020551 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Tests for the deprecated :mod:`repoze.what.authorize` module. """ import unittest from repoze.what.authorize import check_authorization from repoze.what.predicates import has_any_permission, has_permission, \ NotAuthorizedError from base import FakeLogger from test_predicates import make_environ class TestAuthorize(unittest.TestCase): """Tests for the repoze.what.authorize module""" def test_check_authorization(self): logger = FakeLogger() environ = make_environ('gustavo', permissions=['watch-tv', 'party', 'eat']) environ['repoze.who.logger'] = logger p = has_any_permission('party', 'scream') check_authorization(p, environ) info = logger.messages['info'] assert "Authorization granted" == info[0] def test_NotAuthorizedError_is_available(self): """ NotAuthorizedError must subclass PredicateError for backwards compatibility. """ from repoze.what.authorize import NotAuthorizedError from repoze.what.predicates import PredicateError assert issubclass(NotAuthorizedError, PredicateError) class TestAuthorizeWithPredicatesBooleanized(unittest.TestCase): """ Tests for repoze.what-pylons' like "predicates booleanized" misfeatures. """ def test_check_authorization_granted_with_predicates_booleanized(self): """Authorization must be granted as usual.""" logger = FakeLogger() environ = make_environ('gustavo', permissions=['watch-tv', 'party', 'eat']) environ['repoze.who.logger'] = logger p = self._get_booleanized_has_permission('party', environ) assert bool(p), "Predicate isn't booleanized" check_authorization(p, environ) info = logger.messages['info'] assert "Authorization granted" == info[0] def test_check_authorization_denies_with_predicates_booleanized(self): """Authorization must be denied as usual.""" logger = FakeLogger() environ = make_environ('gustavo') environ['repoze.who.logger'] = logger p = self._get_booleanized_has_permission('party', environ) assert not bool(p), "Predicate isn't booleanized" try: check_authorization(p, environ) self.fail('Authorization must have been rejected') except NotAuthorizedError, e: error_msg = 'The user must have the "party" permission' self.assertEqual(str(e), error_msg) # Testing the logs: info = logger.messages['info'] assert "Authorization denied: %s" % error_msg == info[0] def _get_booleanized_has_permission(self, permission, environ): """Return a has_permission instance, booleanized""" class booleanized_has_permission(has_permission): def __nonzero__(self): return self.is_met(environ) return booleanized_has_permission(permission) repoze.what-1.0.9/tests/test_adapters.py0000644000175000017500000003567211356575761020344 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for the base source adapters.""" import unittest from zope.interface import implements from repoze.what.adapters import * from base import FakeGroupSourceAdapter class TestBaseSourceAdapter(unittest.TestCase): """ Tests for the base source adapter. The most important thing to be checked is that it deals with its internal cache correctly. """ def setUp(self): self.adapter = FakeGroupSourceAdapter() def test_cache_is_empty_initially(self): """No section has been loaded; the cache is clear""" self.assertEqual(self.adapter.loaded_sections, {}) self.assertEqual(self.adapter.all_sections_loaded, False) def test_items_are_returned_as_sets(self): """The items of a section must always be returned as a Python set""" # Bulk fetch: for section in self.adapter.get_all_sections(): assert isinstance(self.adapter.get_section_items(section), set) def test_retrieving_all_sections(self): self.assertEqual(self.adapter.get_all_sections(), self.adapter.fake_sections) # Sections are in the cache now self.assertEqual(self.adapter.loaded_sections, self.adapter.fake_sections) self.assertEqual(self.adapter.all_sections_loaded, True) def test_getting_section_items(self): self.assertEqual(self.adapter.get_section_items(u'trolls'), self.adapter.fake_sections[u'trolls']) def test_getting_items_of_non_existing_section(self): self.assertRaises(NonExistingSectionError, self.adapter.get_section_items, 'non-existing') def test_setting_section_items(self): items = (u'guido', u'rasmus') self.adapter.set_section_items(u'trolls', items) self.assertEqual(self.adapter.fake_sections[u'trolls'], set(items)) def test_cache_is_updated_after_setting_section_items(self): # Loading for the first time: self.adapter.get_section_items(u'developers') # Adding items... items = (u'linus', u'rms') self.adapter.set_section_items(u'developers', items) # Checking the cache: self.assertEqual(self.adapter.get_section_items(u'developers'), set(items)) def test_getting_sections_by_criteria(self): credentials = {'repoze.what.userid': u'sballmer'} sections = set([u'trolls']) self.assertEqual(self.adapter.find_sections(credentials), sections) def test_adding_one_item_to_section(self): self.adapter.include_item(u'developers', u'rasmus') self.assertEqual(self.adapter.fake_sections[u'developers'], set((u'linus', u'rasmus', u'rms'))) def test_adding_many_items_to_section(self): self.adapter.include_items(u'developers', (u'sballmer', u'guido')) self.assertEqual(self.adapter.fake_sections[u'developers'], set((u'rms', u'sballmer', u'linus', u'guido'))) def test_cache_is_updated_after_adding_item(self): # Loading for the first time: self.adapter.get_section_items(u'developers') # Now let's add the item: self.adapter.include_item(u'developers', u'guido') self.assertEqual(self.adapter.fake_sections[u'developers'], set((u'linus', u'guido', u'rms'))) # Now checking that the cache was updated: self.assertEqual(self.adapter.fake_sections[u'developers'], self.adapter.get_section_items(u'developers')) def test_removing_one_item_from_section(self): self.adapter.exclude_item(u'developers', u'linus') self.assertEqual(self.adapter.fake_sections[u'developers'], set([u'rms'])) def test_removing_many_items_from_section(self): self.adapter.exclude_items(u'developers', (u'linus', u'rms')) self.assertEqual(self.adapter.fake_sections[u'developers'], set()) def test_cache_is_updated_after_removing_item(self): # Loading for the first time: self.adapter.get_section_items(u'developers') # Now let's remove the item: self.adapter.exclude_item(u'developers', u'rms') self.assertEqual(self.adapter.fake_sections[u'developers'], set([u'linus'])) # Now checking that the cache was updated: self.assertEqual(self.adapter.fake_sections[u'developers'], self.adapter.get_section_items(u'developers')) def test_creating_section(self): self.adapter.create_section('sysadmins') self.assertTrue('sysadmins' in self.adapter.fake_sections) self.assertEqual(self.adapter.fake_sections['sysadmins'], set()) def test_creating_existing_section(self): self.assertRaises(ExistingSectionError, self.adapter.create_section, 'developers') def test_cache_is_updated_after_creating_section(self): self.adapter.create_section('sysadmins') self.assertEqual(self.adapter.get_section_items('sysadmins'), set()) def test_editing_section(self): items = self.adapter.fake_sections['developers'] self.adapter.edit_section(u'developers', u'designers') self.assertEqual(self.adapter.fake_sections[u'designers'], items) def test_editing_non_existing_section(self): self.assertRaises(NonExistingSectionError, self.adapter.edit_section, u'this_section_doesnt_exit', u'new_name') def test_cache_is_updated_after_editing_section(self): # Loading for the first time: self.adapter.get_section_items('developers') # Editing: description = u'Those who write in weird languages' items = self.adapter.fake_sections[u'developers'] self.adapter.edit_section(u'developers', u'coders') # Checking cache: self.assertEqual(self.adapter.get_section_items(u'coders'), items) def test_deleting_section(self): self.adapter.delete_section(u'developers') self.assertRaises(NonExistingSectionError, self.adapter.get_section_items, u'designers') def test_deleting_non_existing_section(self): self.assertRaises(NonExistingSectionError, self.adapter.delete_section, u'this_section_doesnt_exit') def test_cache_is_updated_after_deleting_section(self): # Loading for the first time: self.adapter.get_section_items(u'developers') # Deleting: self.adapter.delete_section(u'developers') # Checking cache: self.assertRaises(NonExistingSectionError, self.adapter.get_section_items, u'developers') def test_checking_section_existence(self): # Existing section: self.adapter._check_section_existence(u'developers') # Non-existing section: self.assertRaises(NonExistingSectionError, self.adapter._check_section_existence, u'designers') def test_checking_section_not_existence(self): # Non-existing section: self.adapter._check_section_not_existence(u'designers') # Existing section: self.assertRaises(ExistingSectionError, self.adapter._check_section_not_existence, u'admins') def test_checking_item_inclusion(self): self.adapter._confirm_item_is_present(u'developers', u'linus') self.assertRaises(ItemNotPresentError, self.adapter._confirm_item_is_present, u'developers', u'maribel') def test_checking_item_inclusion_in_non_existing_section(self): self.assertRaises(NonExistingSectionError, self.adapter._confirm_item_is_present, u'users', u'linus') def test_checking_item_exclusion(self): self.adapter._confirm_item_not_present(u'developers', u'maribel') self.assertRaises(ItemPresentError, self.adapter._confirm_item_not_present, u'developers', u'linus') def test_checking_item_exclusion_in_non_existing_section(self): self.assertRaises(NonExistingSectionError, self.adapter._confirm_item_is_present, u'users', u'linus') class TestBaseSourceAdapterAbstract(unittest.TestCase): """ Tests for the base source adapter's abstract methods. """ def setUp(self): self.adapter = BaseSourceAdapter() def test_get_all_sections(self): self.assertRaises(NotImplementedError, self.adapter._get_all_sections) def test_get_section_items(self): self.assertRaises(NotImplementedError, self.adapter._get_section_items, None) def test_find_sections(self): self.assertRaises(NotImplementedError, self.adapter._find_sections, None) def test_include_items(self): self.assertRaises(NotImplementedError, self.adapter._include_items, None, None) def test_exclude_items(self): self.assertRaises(NotImplementedError, self.adapter._exclude_items, None, None) def test_item_is_included(self): self.assertRaises(NotImplementedError, self.adapter._item_is_included, None, None) def test_create_section(self): self.assertRaises(NotImplementedError, self.adapter._create_section, None) def test_edit_section(self): self.assertRaises(NotImplementedError, self.adapter._edit_section, None, None) def test_delete_section(self): self.assertRaises(NotImplementedError, self.adapter._delete_section, None) def test_section_exists(self): self.assertRaises(NotImplementedError, self.adapter._section_exists, None) def test_adapter_is_writable_by_default(self): self.assert_(self.adapter.is_writable) class TestNotWritableSourceAdapter(unittest.TestCase): """Tests for an adapter dealing with a read-only source""" def setUp(self): self.adapter = FakeGroupSourceAdapter(writable=False) def test_setting_items(self): self.assertRaises(SourceError, self.adapter.set_section_items, u'admins', ['gnu', 'tux']) def test_settings_items_in_non_existing_section(self): """The section existence must be checked first""" self.assertRaises(NonExistingSectionError, self.adapter.set_section_items, u'mascots', ['gnu', 'tux']) def test_include_items(self): self.assertRaises(SourceError, self.adapter.include_item, u'admins', 'tux') self.assertRaises(SourceError, self.adapter.include_items, u'admins', ['gnu', 'tux']) def test_include_items_in_non_existing_section(self): """The section existence must be checked first""" self.assertRaises(NonExistingSectionError, self.adapter.include_item, u'mascots', 'gnu') self.assertRaises(NonExistingSectionError, self.adapter.include_items, u'mascots', ['gnu', 'tux']) def test_include_existing_items(self): """The items existence must be checked first""" self.assertRaises(ItemPresentError, self.adapter.include_item, u'developers', 'rms') self.assertRaises(ItemPresentError, self.adapter.include_items, u'developers', ['rms', 'linus']) def test_exclude_items(self): self.assertRaises(SourceError, self.adapter.exclude_item, u'admins', u'rms') self.assertRaises(SourceError, self.adapter.exclude_items, u'developers', [u'rms', u'linus']) def test_exclude_items_in_non_existing_section(self): """The section existence must be checked first""" self.assertRaises(NonExistingSectionError, self.adapter.exclude_item, u'mascots', 'gnu') self.assertRaises(NonExistingSectionError, self.adapter.exclude_items, u'mascots', ['gnu', 'tux']) def test_exclude_existing_items(self): """The items existence must be checked first""" self.assertRaises(ItemNotPresentError, self.adapter.exclude_item, u'developers', 'rasmus') self.assertRaises(ItemNotPresentError, self.adapter.exclude_items, u'developers', ['guido', 'rasmus']) def test_create_section(self): self.assertRaises(SourceError, self.adapter.create_section, u'mascots') def test_create_existing_section(self): """The section existence must be checked first""" self.assertRaises(ExistingSectionError, self.adapter.create_section, u'admins') def test_edit_section(self): self.assertRaises(SourceError, self.adapter.edit_section, u'admins', u'administrators') def test_edit_non_existing_section(self): """The section existence must be checked first""" self.assertRaises(NonExistingSectionError, self.adapter.edit_section, u'mascots', u'animals') def test_delete_section(self): self.assertRaises(SourceError, self.adapter.delete_section, u'admins') def test_delete_non_existing_section(self): """The section existence must be checked first""" self.assertRaises(NonExistingSectionError, self.adapter.delete_section, u'mascots') def test_adapter_is_not_writable(self): self.assertFalse(self.adapter.is_writable) repoze.what-1.0.9/tests/base.py0000644000175000017500000001251111356575761016377 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2008-2009, Gustavo Narea # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Utilities for the test suite of :mod:`repoze.what`. """ from repoze.what.middleware import setup_auth from repoze.what.adapters import BaseSourceAdapter __all__ = ['FakeAuthenticator', 'FakeGroupSourceAdapter', 'FakePermissionSourceAdapter', 'FakeLogger', 'encode_multipart_formdata'] class FakeAuthenticator(object): """ Fake :mod:`repoze.who` authenticator plugin. It will authenticate if you use one of the following credentials (username and password): * ``rms``: ``freedom`` * ``linus``: ``linux`` * ``sballmer``: ``developers`` * ``guido``: ``pythonic`` * ``rasmus``: ``php`` """ credentials = { u'rms': u'freedom', u'linus': u'linux', u'sballmer': u'developers', u'guido': u'pythonic', u'rasmus': u'php' } def authenticate(self, environ, identity): login = identity['login'] pass_ = identity['password'] if login in self.credentials and pass_ == self.credentials[login]: return login class FakeGroupSourceAdapter(BaseSourceAdapter): """ Mock group source adapter. The `fake` source it handles contains the following groups: * ``admins``: ``rms`` * ``developers``: ``rms``, ``linus`` * ``trolls``: ``sballmer`` * ``python``: `(empty)` * ``php``: `(empty)` """ def __init__(self, *args, **kwargs): super(FakeGroupSourceAdapter, self).__init__(*args, **kwargs) self.fake_sections = { u'admins': set([u'rms']), u'developers': set([u'rms', u'linus']), u'trolls': set([u'sballmer']), u'python': set(), u'php': set() } def _get_all_sections(self): return self.fake_sections def _get_section_items(self, section): return self.fake_sections[section] def _find_sections(self, credentials): username = credentials['repoze.what.userid'] return set([n for (n, g) in self.fake_sections.items() if username in g]) def _include_items(self, section, items): self.fake_sections[section] |= items def _exclude_items(self, section, items): for item in items: self.fake_sections[section].remove(item) def _item_is_included(self, section, item): return item in self.fake_sections[section] def _create_section(self, section): self.fake_sections[section] = set() def _edit_section(self, section, new_section): self.fake_sections[new_section] = self.fake_sections[section] del self.fake_sections[section] def _delete_section(self, section): del self.fake_sections[section] def _section_exists(self, section): return section in self.fake_sections class FakePermissionSourceAdapter(FakeGroupSourceAdapter): """ `Mock` permissions source adapter. The `fake` source it handles contains the following permissions: * ``see-site``: ``trolls`` * ``edit-site: ``admins``, ``developers`` * ``commit``: ``developers`` """ def __init__(self, *args, **kwargs): super(FakePermissionSourceAdapter, self).__init__(*args, **kwargs) self.fake_sections = { u'see-site': set([u'trolls']), u'edit-site': set([u'admins', u'developers']), u'commit': set([u'developers']) } def _find_sections(self, group_name): return set([n for (n, p) in self.fake_sections.items() if group_name in p]) class FakeLogger(object): """A mock Python logger.""" def __init__(self): self.messages = { 'critical': [], 'error': [], 'warning': [], 'info': [], 'debug': [] } def critical(self, msg): self.messages['critical'].append(msg) def error(self, msg): self.messages['error'].append(msg) def warning(self, msg): self.messages['warning'].append(msg) def info(self, msg): self.messages['info'].append(msg) def debug(self, msg): self.messages['debug'].append(msg) # This function was stolen from repoze.who.tests: def encode_multipart_formdata(fields): BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' CRLF = '\r\n' L = [] for (key, value) in fields: L.append('--' + BOUNDARY) L.append('Content-Disposition: form-data; name="%s"' % key) L.append('') L.append(value) L.append('--' + BOUNDARY + '--') L.append('') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body repoze.what-1.0.9/tests/test_adapters_testutil.py0000644000175000017500000000433511356575761022271 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """Test suite for the adapters' test utilities.""" import unittest from repoze.what.adapters.testutil import GroupsAdapterTester, \ PermissionsAdapterTester, \ ReadOnlyGroupsAdapterTester, \ ReadOnlyPermissionsAdapterTester from base import FakeGroupSourceAdapter, FakePermissionSourceAdapter #{ Read and write adapters class TestGroupsAdapterTester(GroupsAdapterTester, unittest.TestCase): def setUp(self): super(TestGroupsAdapterTester, self).setUp() self.adapter = FakeGroupSourceAdapter() class TestPermissionsAdapterTester(PermissionsAdapterTester, unittest.TestCase): def setUp(self): super(TestPermissionsAdapterTester, self).setUp() self.adapter = FakePermissionSourceAdapter() #{ Read-only adapters class TestReadOnlyGroupsAdapterTester(ReadOnlyGroupsAdapterTester, unittest.TestCase): def setUp(self): super(TestReadOnlyGroupsAdapterTester, self).setUp() self.adapter = FakeGroupSourceAdapter(writable=False) class TestReadOnlyPermissionsAdapterTester(ReadOnlyPermissionsAdapterTester, unittest.TestCase): def setUp(self): super(TestReadOnlyPermissionsAdapterTester, self).setUp() self.adapter = FakePermissionSourceAdapter(writable=False) #} repoze.what-1.0.9/VERSION.txt0000644000175000017500000000000611356575761015633 0ustar gustavogustavo1.0.9 repoze.what-1.0.9/README.txt0000644000175000017500000000237611356575761015457 0ustar gustavogustavo************************************************** repoze.what -- Authorization for WSGI applications ************************************************** :mod:`repoze.what` is an `authorization framework` for WSGI applications, based on :mod:`repoze.who` (which deals with `authentication` and `identification`). On the one hand, it enables an authorization system based on the groups to which the `authenticated or anonymous` user belongs and the permissions granted to such groups by loading these groups and permissions into the request on the way in to the downstream WSGI application. And on the other hand, it enables you to manage your groups and permissions from the application itself or another program, under a backend-independent API. For example, it would be easy for you to switch from one back-end to another, and even use this framework to migrate the data. This is just the authorization pattern it supports out-of-the-box, but you can may it support other authorization patterns with your own predicates. It's highly extensible, so it's very unlikely that it will get in your way -- Among other things, you can extend it to check for many conditions (such as checking that the user comes from a given country, based on her IP address, for example). repoze.what-1.0.9/docs/0000755000175000017500000000000011356575761014701 5ustar gustavogustavorepoze.what-1.0.9/docs/Makefile0000644000175000017500000000431311356575761016342 0ustar gustavogustavo# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html web pickle htmlhelp latex changes linkcheck help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pickle to make pickle files (usable by e.g. sphinx-web)" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf build/* html: mkdir -p build/html build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html @echo @echo "Build finished. The HTML pages are in build/html." pickle: mkdir -p build/pickle build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle @echo @echo "Build finished; now you can process the pickle files or run" @echo " sphinx-web build/pickle" @echo "to start the sphinx-web server." web: pickle htmlhelp: mkdir -p build/htmlhelp build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in build/htmlhelp." latex: mkdir -p build/latex build/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex @echo @echo "Build finished; the LaTeX files are in build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: mkdir -p build/changes build/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes @echo @echo "The overview file is in build/changes." linkcheck: mkdir -p build/linkcheck build/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in build/linkcheck/output.txt." repoze.what-1.0.9/docs/source/0000755000175000017500000000000011356575761016201 5ustar gustavogustavorepoze.what-1.0.9/docs/source/index.rst0000644000175000017500000001465411356575761020054 0ustar gustavogustavo:mod:`repoze.what` -- Authorization for WSGI applications ========================================================= .. module:: repoze.what :synopsis: Authorization framework for WSGI applications .. moduleauthor:: Gustavo Narea :Author: Gustavo Narea. :Latest version: |release| .. topic:: Overview :mod:`repoze.what` is an `authorization framework` for WSGI applications, based on :mod:`repoze.who` (which deals with `authentication` and `identification`). On the one hand, it enables an authorization system based on the groups to which the `authenticated or anonymous` user belongs and the permissions granted to such groups by loading these groups and permissions into the request on the way in to the downstream WSGI application. And on the other hand, it enables you to manage your groups and permissions from the application itself or another program, under a backend-independent API. For example, it would be easy for you to switch from one back-end to another, and even use this framework to migrate the data. This is just the authorization pattern it supports out-of-the-box, but you can may it support other authorization patterns with your own :term:`predicates `. It's highly extensible, so it's very unlikely that it will get in your way -- Among other things, you can extend it to check for many conditions (such as checking that the user comes from a given country, based on her IP address, for example). Features ======== Unless mentioned otherwise, the following features are available in :mod:`repoze.what` and its official plugins: * ``Web framework independent``. You can use it on any WSGI application and any WSGI framework (or no framework at all). Web frameworks may provide integration with it (like `TurboGears 2 `_, which features a strong integration with :mod:`repoze.what`). * ``Authorization only``. It doesn't try to be an all-in-one auth monster -- it will only do `authorization` and nothing else. * ``Highly extensible``. It's been created with extensibility in mind, so that it won't get in your way and you can control authorization however you want or need, either with official components, third party plugins or your own plugins. * ``Fully documented``. If it's not described in the manual, it doesn't exist. Everything is documented along with examples. * ``Reliable``. We are committed to keep the code coverage at 100%. * ``Control access to any resource``. Although it's only recommended to control authorization on action controllers, you can also use it to restrict access to other things in your package (e.g., only allow access to a database table if the current user is the admin). * If you use the groups/permissions-based authorization pattern, your application's `groups` and `permissions` may be stored in an SQLAlchemy or Elixir-managed database, in ``.ini`` files or in XML files (although you may also create your own :mod:`adapters `!). * The only requirement is that you use the powerful and extensible :mod:`repoze.who` authentication framework (which can be configured for you with the :mod:`quickstart `_, we *will* have official plugins to: * Enable `OAuth `_ support. * Enable authorization based on certain network conditions (e.g., grant access if the user's IP address belongs to a given IP range, deny access if the user's host name is "example.org", grant access based on the user's ISP). * Enable authorization based on `client-side SSL certificates `_ (e.g., allow access if the `Certificate Authority` is XYZ, allow access if the user is called "John Smith" or "Foo Bar"). * Enable authorization based on LDAP attributes of the authenticated user's entry (e.g., allow access if the user can be reached at a cellular phone, allow access if the user belongs to the "ABC" organization), as well as the ability to re-use LDAP `Organizational Units` as groups. * Enable a highly extensible `CAPTCHA `_ driven authorization mechanism to restrict access to a given resource (possibly the hardest to create plugin). * Store groups in ``Htgroups``. .. _install: How to install ============== The only requirement of :mod:`repoze.what` is :mod:`repoze.who` and you can install both by running:: easy_install repoze.what The development mainline is available at the following Subversion repository:: http://svn.repoze.org/repoze.what/branches/1.X/ Framework-specific documentation ================================ The following documents will help you implement :mod:`repoze.what` in your framework (if any): * `TurboGears `_: :mod:`repoze.who` and :mod:`repoze.what` are the default authentication and authorization frameworks (respectively) in TurboGears 2 applications. `Learn more about them inside TurboGears `_. * `Authorization with repoze.what on Pylons `_. If you have written documents to implement :mod:`repoze.what` in a web framework, please `let us know `_ to get a link here. How to get help? ================ The prefered place to ask questions is the `Repoze mailing list `_ or the `#repoze `_ IRC channel. Bugs reports and feature requests should be sent to `the issue tracker of the Repoze project `_. If you have problems, please don't forget to include the output of your application with the ``AUTH_LOG`` environment variable set to ``1`` when you get in touch with us. For example, if your application is based on TurboGears or Pylons, you may run it with the following command:: AUTH_LOG=1 paster serve --reload development.ini Contents ======== .. toctree:: :maxdepth: 2 Manual/index News Participate Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` repoze.what-1.0.9/docs/source/conf.py0000644000175000017500000001407111356575761017503 0ustar gustavogustavo# -*- coding: utf-8 -*- # # repoze.what documentation build configuration file, created by # sphinx-quickstart on Mon Nov 10 20:27:30 2008. # # This file is execfile()d with the current directory set to its containing dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # # All configuration values have a default value; values that are commented out # serve to show the default value. import sys, os here = os.path.dirname(os.path.abspath(__file__)) root = os.path.dirname(os.path.dirname(here)) # If setting up the auto(module|class) functionality: sys.path.append(os.path.abspath(root)) wd = os.getcwd() os.chdir(root) os.system('%s setup.py test -q' % sys.executable) os.chdir(wd) for item in os.listdir(root): if item.endswith('.egg'): sys.path.append(os.path.join(root, item)) # General configuration # --------------------- extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General substitutions. project = u'repoze.what' copyright = u'2008, The Repoze Project' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. release = open(os.path.join(root, 'VERSION.txt')).readline().rstrip() # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directories, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # Options for HTML output # ----------------------- # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. html_style = 'repoze.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '_static/logo_hi.gif' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, the reST sources are included in the HTML build as _sources/. #html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'repozewhatdoc' # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). latex_documents = [ ('index', 'repozewhat.tex', u'repoze.what Documentation', u'Gustavo Narea', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True intersphinx_mapping = { 'http://static.repoze.org/whodocs/': None, 'http://code.gustavonarea.net/repoze.what.plugins.sql/': None, 'http://code.gustavonarea.net/repoze.what.plugins.xml/': None, 'http://code.gustavonarea.net/repoze.what-pylons/': None, 'http://code.gustavonarea.net/repoze.who-testutil/': None, } repoze.what-1.0.9/docs/source/News.rst0000644000175000017500000003651311356575761017657 0ustar gustavogustavo*************************** :mod:`repoze.what` releases *************************** This document describes the releases of :mod:`repoze.what`. .. _repoze.what-1.0.9: :mod:`repoze.what` 1.0.9 (2010-03-04) ===================================== * Pin ``repoze.who`` to versions <= 1.99 (2.0a1 is incompatible). * Made tests compatible with released :mod:`repoze.who` 1.0.x. .. _repoze.what-1.0.8: :mod:`repoze.what` 1.0.8 (2009-03-14) ===================================== * Fixed problem with `routing_args `_ compliance in :meth:`repoze.what.predicates.Predicate.parse_variables`; a wrong structure of that variable was assumed. * Fixed small internal problem with the credentials dictionary. .. _repoze.what-1.0.7: :mod:`repoze.what` 1.0.7 (2009-03-13) ===================================== * Added the :class:`is_anonymous ` predicate. .. _repoze.what-1.0.6: :mod:`repoze.what` 1.0.6 (2009-03-05) ===================================== * The deprecated :func:`repoze.what.authorize.check_authorization` didn't evaluate predicates correctly if predicates were :func:`booleanized `. Thanks to Michael Brickenstein! .. attention:: **Don't panic!** This can hardly affect somebody on the earth: If you're using :func:`repoze.what.authorize.check_authorization` directly, then you're not using Pylons, and if you're not using Pylons, then you're not using :mod:`repoze.what-pylons `. However, this does affect you if you're not on the earth, use :func:`repoze.what.authorize.check_authorization` and "booleanize predicates" in your non-Pylons-based framework ;-) .. _repoze.what-1.0.5: :mod:`repoze.what` 1.0.5 (2009-03-02) ===================================== * To ease testing, now :func:`repoze.what.middleware.setup_auth` uses :func:`repoze.who.plugins.testutil.make_middleware` instead of calling :class:`repoze.who.middleware.PluggableAuthenticationMiddleware` directly. * Now non-ASCII messages can be logged without problems in Python < 2.6. Thanks to Christoph Zwerschke (`TG Issue #2250 `_). * Minor updates in the documentation. .. _repoze.what-1.0.4: :mod:`repoze.what` 1.0.4 (2009-02-06) ===================================== * Now request-sensitive predicate checkers are easier to write because of the introduction of the :meth:`repoze.what.predicates.Predicate.parse_variables` method, which is aware of the `wsgiorg.routing_args specification `_. * Now :meth:`repoze.what.predicates.Predicate.unmet` receives an optional argument to override the error message. This feature is backported from v2. * Backported :meth:`repoze.what.predicates.Predicate.is_met` from :mod:`repoze.what` v2. * Improved the :term:`predicates ` section in the manual. * For forward compatibility with :mod:`repoze.what` v2, the :mod:`repoze.what.authorize` module is deprecated. If you want to use :mod:`repoze.what` v2, you should start using :meth:`repoze.what.predicates.Predicate.check_authorization` and :class:`repoze.what.predicates.NotAuthorizedError` instead of :meth:`repoze.what.authorize.check_authorization` and :class:`repoze.what.authorize.NotAuthorizedError`, respectively. .. _repoze.what-1.0.3: :mod:`repoze.what` 1.0.3 (2009-01-28) ===================================== This is a bug fix release, there is no new feature implemented. * For forward compatibility with v2, the latest version of the Ini, SQL and XML :term:`group adapters ` rely on the ``repoze.what.userid`` key in the :mod:`repoze.what` ``credentials`` dictionary. However, :mod:`repoze.what` was passing the :mod:`repoze.who` ``identity`` to them instead of its ``credentials`` dict. .. _repoze.what-1.0.2: :mod:`repoze.what` 1.0.2 (2009-01-23) ===================================== For forward compatibility with :mod:`repoze.what` v2.0, :mod:`predicates ` should define the :meth:`evaluate ` method which deprecates :meth:`_eval_with_environ ` as of this release. This indirectly fixes a thread-safety bug found by Alberto Valverde on :class:`Any `-based predicates when used along with :class:`All `-based ones. Thank you very much once again, Alberto! .. _repoze.what-1.0.1: :mod:`repoze.what` 1.0.1 (2009-01-21) ===================================== This release fixes an important bug which *may* affect production Web sites depending on how you use the ``All`` predicate or any of its derivatives (``has_all_permissions`` and ``in_all_groups``). TurboGears 2 applications are all affected, at least by default. The likelihood that this will affect your application is very high, so upgrading is highly recommended if it's on production. * Some :mod:`repoze.what` :mod:`predicates ` were not thread-safe when they were instantiated in a module and then shared among threads (as used in TurboGears 2). This was found by and solved with the help of `Alberto Valverde `_ (¡Gracias, Alberto!). We fixed this by making :meth:`repoze.what.predicates.Predicate.eval_with_predicate` raise an exception if the predicate is not met, instead of returning a boolean and setting the ``error`` instance attribute of the predicate to the predicate failure message. So if you are using that method directly, instead of using :func:`repoze.what.authorize.check_authorization`, this is a backwards incompatible change for you and thus you should update your code. If you check predicates like this (which is discouraged; see :func:`repoze.what.authorize.check_authorization`):: from repoze.what.predicates import is_user, in_group, All p = All(is_user('someone'), in_group('some-group')) environ = gimme_the_environ() if p.eval_with_environ(environ): print('Authorization is denied: %s' % p.error) else: print('Authorization is granted') Then you should update your code like this:: # This way of checking predicates is DISCOURAGED. Use # repoze.what.authorize.check_authorization() instead. from repoze.what.predicates import is_user, in_group, All, PredicateError p = All(is_user('someone'), in_group('some-group')) environ = gimme_the_environ() try: p.eval_with_environ(environ) print('Authorization is granted') except PredicateError, error: print('Authorization is denied: %s' % error) .. note:: Because of this, TurboGears 2 users who want to use this release, should try the latest revision in the TG2 Subversion repository or wait for TurboGears-2.0b4. But again, there's no hurry if your application is not in production. * For forward compatibility with :mod:`repoze.what` v2, the user id used in the built-in predicates is that found in ``environ['repoze.what.credentials']['repoze.what.userid']`` and the adapters loaded are now available at ``environ['repoze.what.adapters']``. This is *not* a backwards incompatible change. .. _repoze.what-1.0: :mod:`repoze.what` 1.0 (2009-01-19) =================================== This is the first stable release of :mod:`repoze.what` and it was announced on the `Repoze blog `_. * Fixed a problem with unicode support in :func:`repoze.what.authorize.check_authorization`, reported by Chen Houwu on TurboGears mailing list. * Added the current user's groups and permissions to the newly-created ``environ['repoze.what.credentials']`` dictionary for forward compatibility with :mod:`repoze.what` v2. Such values are still defined in the :mod:`repoze.who` ``identity`` dictionary, but its use is highly discouraged as of this release. See :mod:`repoze.what.middleware`. * Applied work-around to fix Python v2.4 and v2.5 support. .. _repoze.what-1.0rc2: :mod:`repoze.what` 1.0rc2 (2008-12-20) ====================================== * Fixed the constructor of the :class:`Not ` predicate, which didn't call its parent and therefore it was not possible to specify a custom message. * From now on, predicates that are not met will have only *one* error message, even in compound predicates. It didn't make sense to have a list of errors and thus this behavior has been changed in this release. This will affect you if you deal with :func:`repoze.what.authorize.check_authorization` directly and handled the errors of :class:`repoze.what.authorize.NotAuthorizedError` as in:: try: check_authorization(predicate, environ) except NotAuthorizedError, exc: for error in exc.errors: print error The code above may be updated this way:: try: check_authorization(predicate, environ) except NotAuthorizedError, exc: print exc .. note:: This doesn't affect TurboGears 2 users because TG itself deals with this function and it's already updated to work with :mod:`repoze.what` 1.0rc2. Keep in mind that for this release to work on TurboGears 2, you need TurboGears 2 Beta 1 (not yet released as of this writing) or the latest revision in the repository. * For forward compatibility, it's no longer mandatory to use the groups/permissions-based authorization pattern in order to use :mod:`repoze.what`. This package should support several authorization patterns and they must all be optional, such as the upcoming support for roles-based authorization in :mod:`repoze.what` 1.5. As a result, now you can skip the definition of group and permission adapters and use :func:`repoze.what.middleware.setup_auth` as a simple proxy for :class:`repoze.who.middleware.PluggableAuthenticationMiddleware`:: app_with_auth = setup_auth( app, identifiers=identifiers, challengers=challengers, mdproviders=mdproviders, classifier=classifier, challenge_decider=challenge_decider ) .. _repoze.what-1.0rc1: :mod:`repoze.what` 1.0rc1 (2008-12-10) ====================================== * Added support for read-only adapters in the :mod:`testutil ` with the :class:`ReadOnlyGroupsAdapterTester ` and :class:`ReadOnlyPermissionsAdapterTester ` test cases. * Fixed Python 3 deprecation warnings. .. _repoze.what.plugins.ini: :mod:`repoze.what.plugins.ini` -- Ini adapters available (2008-12-09) ===================================================================== José Dinuncio has made a *great* work writing :term:`group ` and :term:`permission ` adapters for Ini files! So, thanks to him, now it's not only possible to store your groups and permissions in databases, but also in files! * Link: http://github.com/jdinuncio/repoze.what.plugins.ini/ .. _repoze.what-1.0b2: :mod:`repoze.what` 1.0b2 (2008-12-04) ===================================== * Added support for read-only sources. See :class:`repoze.what.adapters.BaseSourceAdapter`. Backwards-incompatible changes ------------------------------ * The signature of :func:`repoze.what.middleware.setup_auth` has changed: Now it simply receives the WSGI application, the group adapters and the permissions adapters -- additional keyword arguments will be sent to :class:`repoze.who.middleware.PluggableAuthenticationMiddleware`. Also, it no longer defines a default identifier or challenger. .. note:: It's very unlikely that this affects your application, as that function is normally used by :func:`repoze.what.plugins.quickstart.setup_sql_auth`. .. _repoze.what-1.0b1: :mod:`repoze.what` 1.0b1 (2008-11-26) ===================================== This is the first release of this package as part of the Repoze project. It started as the :mod:`repoze.who` extension for TurboGears 2 applications (:mod:`tg.ext.repoze.who`, doing authenticatication and authorization) by Chris McDonough, Florent Aide and Christopher Perkins, then Gustavo Narea took over the project to make it deal with authorization only and add support to store `groups` and `permissions` in other types of sources (among other things) under the :mod:`tgext.authorization` namespace, but finally it was turned into a Repoze project in order to make it available in arbitrary WSGI applications. * Removed dependencies on TurboGears and Pylons. * Introduced a framework-independent function (:func:`repoze.what.authorize.check_authorization`) to check authorization based on a predicate and the WSGI environment, along with the :class:`repoze.what.authorize.NotAuthorizedError` exception. * Now :mod:`repoze.what` is 100% documented. * Moved the predicates from :mod:`repoze.what.authorize` to :mod:`repoze.what.predicates`. Nevertheless, they are imported in the former to avoid breaking TurboGears 2 applications created when :mod:`tg.ext.repoze.who` or :mod:`tgext.authorization` existed. * Added the :class:`Not ` predicate. * Now you can override the error message of the built-in predicates or set your own message at instantiation time by passing the ``msg`` keywork argument to the predicate. Example:: from repoze.what.predicates import is_user my_predicate = is_user('carla', msg="Only Carla may come here") As a result, if your custom predicate defines the constructor method (``__init__``), then you're highly encouraged to call its parent with the ``msg`` keyword argument. Example:: from repoze.what.predicates import Predicate class MyCoolPredicate(Predicate): def __init__(self, **kwargs): super(MyCoolPredicate, self).__init__(**kwargs) * Moved the SQL plugin (:mod:`repoze.what.plugins.sql`) into a separate package. Also moved :mod:`repoze.what.plugins.quickstart` into that package because it's specific to the SQL plugin. * Log messages are no longer sent to standard output if the ``WHO_LOG`` environment variable is defined, but with ``AUTH_LOG``. * Now :mod:`repoze.what` uses logging internally to ease debugging. Backwards-incompatible changes ------------------------------ * If you have custom predicates, you should update the ``eval_with_object`` method, which has been renamed to ``_eval_with_environ`` and only receives one argument (the WSGI environment). This is, if your method's signature looks like this:: eval_with_object(obj, errors) Now it should look like this:: _eval_with_environ(environ) Note that ``errors`` are no longer passed. On the other hand, the ``error_message`` attribute of predicates has been renamed to ``message`` because they are not only used to display errors (see :mod:`repoze.what.predicates`). * The :func:`repoze.what.authorize.require` decorator has been removed because it's specific to TurboGears. TurboGears 2 applications will find it at :func:`tg.require`. Because this is the first beta release, there should not be more backwards incompatible changes in the coming 1.X releases. repoze.what-1.0.9/docs/source/Participate.rst0000644000175000017500000000625211356575761021205 0ustar gustavogustavo***************************************************** Participate in the development of :mod:`repoze.what`! ***************************************************** .. topic:: Overview Here you will learn how you may contribute to :mod:`repoze.what`, either extending it or fixing issues. Sending patches =============== Please feel free to send patches to fix bugs or implement features, but keep in mind that it will take some time to get applied if it doesn't follow our basic `coding conventions`_. If you can, please include the respective tests too. Patches should be sent to `the Repoze mailing list `_. Writing plugins =============== An important way to contribute to :mod:`repoze.what` is by creating :mod:`plugins `. There are no special guidelines to create unofficial plugins, but you are highly encouraged to create plugins under the :mod:`repoze.what.plugins` namespace and contact us once you have at least one usable release (so that we can mention it in the manual). Guidelines for official plugins ------------------------------- Official plugins must meet certain requirements: * It must use `the Repoze license `_. * It should follow the `coding conventions`_ of the project. * Its documentation should be merged into the official documentation. If you want to turn your :mod:`repoze.what` unofficial plugin into an official one and you are willing to make it meet the requirements above, please propose it after you have at least one usable release (e.g., a beta, a release candidate; not only pre-alphas) -- The rate of `stillborn` Free Software projects is very high, so we prefer to turn a unofficial plugin into an official one if it has ever seen the light. Coding conventions ================== The basic coding conventions for :mod:`repoze.what` are not special at all -- they are basically the same you will find in other Python projects: * The character encoding should be UTF-8. * Lines should not contain more than 80 characters. * The new line character should be the one used in Unix systems (``\n``). * Stick to the `widely` used `Style Guide for Python Code `_ and `Docstring Conventions `_. However, we have the following additional coding conventions which should be applied in the first beta release of a package `at the latest` (this is, they are not strictly necessary in alpha releases): * The unit test suite for the package should cover 100% of the code. `People entrust us with nothing less than the authorization control of their application`, so we should take this additional security step to deserve their trust. Sure, it won't make the package 100% bug-free (that's impossible), but at least we'll avoid regression bugs effectively and we'll be sure that a bug found will be an unwritten test. It shouldn't be hard for you if you practice the Test-Driven Development methodology. * `All` the public components of the package should be properly documented along with examples, so that people won't have to dive into our code to learn how to achieve what they want. repoze.what-1.0.9/docs/source/Manual/0000755000175000017500000000000011356575761017416 5ustar gustavogustavorepoze.what-1.0.9/docs/source/Manual/index.rst0000644000175000017500000000050211356575761021254 0ustar gustavogustavo***************************** The :mod:`repoze.what` Manual ***************************** :Author: Gustavo Narea. :Version: |version| Below are the contents for the :mod:`repoze.what` manual: .. toctree:: :maxdepth: 3 GettingStarted Predicates/index ManagingSources Plugins/index InnerWorkings repoze.what-1.0.9/docs/source/Manual/Plugins/0000755000175000017500000000000011356575761021037 5ustar gustavogustavorepoze.what-1.0.9/docs/source/Manual/Plugins/index.rst0000644000175000017500000001112711356575761022702 0ustar gustavogustavo********************************************************************** :mod:`repoze.what.plugins` -- Available plugins for :mod:`repoze.what` ********************************************************************** .. module:: repoze.what.plugins :synopsis: repoze.what plugins .. moduleauthor:: Gustavo Narea .. topic:: Overview :mod:`repoze.what` itself strives to be a rather minimalist project which only depends on :mod:`repoze.who` and built-in Python modules, so that it will never get in your way and thus make it rather easy for third parties to extend it to suit their needs. As a consequence, it doesn't ship with support for :term:`adapters `, so you should install the relevant plugins to manage your groups and permissions. There are three types of plugins: .. glossary:: adapters plugin It's a plugin that provides one :term:`group adapter` and/or one :term:`permission adapter` for a given back-end, but it may also provide additional functionality specific to said back-end. For example, the SQL plugin provides group and permission adapters that enable you to store your groups and permissions in databases, as well as a module with some utilities to get started with :mod:`repoze.who` and :mod:`repoze.what` very quickly. predicates plugin It's a plugin that provides one or more :term:`predicate checkers `. For example, the network plugin will provide :term:`predicate checkers ` to make sure that only people from the intranet can access certain parts of a web site. extras plugin It's a plugin that enables a functionality not available in :mod:`repoze.what` out-of-the-box (other than providing :term:`adapters ` or :term:`predicate checkers `). For example, the quickstart (:mod:`repoze.what.plugins.quickstart`). The classification above is not mutually exclusive: If a plugin provides :term:`adapters `, :term:`predicate checkers ` and extra functionality, then it can be referred to as "a predicates, adapters and extras plugin". For instance, the :mod:`SQL plugin ` is both an adapters and extras plugin because it provides the :mod:`quickstart ` plugin. Available :term:`adapters plugins ` ==================================================== ============================== ================ =============== ================ ==================== Plugin name Source type Write support Groups adapter Permissions adapter ============================== ================ =============== ================ ==================== repoze.what.plugins.ini [#f1]_ ``.ini`` files No Yes Yes :mod:`repoze.what.plugins.sql` SQL Yes Yes Yes :mod:`repoze.what.plugins.xml` XML files Yes Yes Yes ============================== ================ =============== ================ ==================== Available :term:`predicates plugins ` ======================================================== None, yet. Available :term:`extras plugins ` ================================================ ====================================== =========================================================================================================== Plugin name Description ====================================== =========================================================================================================== :mod:`repoze.what.plugins.quickstart` Pre-configured authentication system to get started with :mod:`repoze.who` and :mod:`repoze.what` quickly :mod:`repoze.what.plugins.pylonshq` :mod:`repoze.what` utilities for Pylons/TG2 applications repoze.what.plugins.config [#f2]_ Configure :mod:`repoze.what` from an ``Ini`` file with Paste Deploy. ====================================== =========================================================================================================== .. rubric:: Footnotes .. [#f1] `repoze.what Ini plugin `_, written by José Dinuncio. .. [#f2] `repoze.what Config plugin `_, written by José Dinuncio. repoze.what-1.0.9/docs/source/Manual/GettingStarted.rst0000644000175000017500000001661711356575761023113 0ustar gustavogustavo*************************************** Getting started with :mod:`repoze.what` *************************************** .. topic:: Overview This document describes the basics of :mod:`repoze.what`, including its terminology and how to configure authentication and authorization easily. Terminology ----------- As explained previously, :mod:`repoze.what`'s base authorization pattern is based on the groups to which the user belongs and the permissions granted to such groups, and such groups and permissions can be stored in different types of sources -- because of that, :mod:`repoze.what` uses a generic terminology when it deals with those sources: .. glossary:: source Where authorization data (groups and/or permissions) is stored. It may be a database or a file (an Htgroups file, an Ini file, etc), for example. group source A :term:`source` that stores groups. For example, an Htgroups file or an Ini file. permission source A :term:`source` that stores permissions. For example, an Ini file. source adapter An object that manages a given type of :term:`source` to add, edit and delete entries under an API independent of the source type. group adapter An :term:`adapter ` that deals with one :term:`group source`. permission adapter An :term:`adapter ` that deals with one :term:`permission source`. section Sections are the `compound elements` that make up a :term:`source` -- this is, in a :term:`permission source`, the sections are the permissions, and in a :term:`group source`, the sections are the groups. item The elements that are contained in a :term:`section`. In a :term:`permission source`, the items are the groups that are granted the permission represented in their parent section; likewise, in a :term:`group source`, the items are the Ids of the users that belong to the group represented in the parent section. The authentication framework (:mod:`repoze.who`) only deals with the :term:`sources ` that handle your users' credentials, while the authorization framework (:mod:`repoze.what`) deals with both the :term:`sources ` that handle your groups and those that handle your permissions. Sample sources ~~~~~~~~~~~~~~ Below are the contents of a mock ``.htgroups`` file that defines the groups of an application. In other words, such a file is a :term:`group source` of type ``htgroups``:: developers: rms, linus, guido admins: rms, linus users: gustavo, maribel It has three sections and five items: "developers" (made up of the items "rms", "linus" and "guido"), "admins" (made up of the items "rms" and "linus") and "users (made up of the items "gustavo" and "maribel"). And below are the contents of a mock ``.ini`` file that defines the permissions of the groups in an application. In other words, such a file is a :term:`permission source` of type ``Ini``:: [manage-site] admins [release-software] admins developers [contact-us] users It has three sections and three items: "manage-site" (made up one item, "admins"), "release-software" (made up of the items "admins" and "developers") and "contact-us" (made up of the item "users"). If you use a database to store your users, groups and permissions, then such a database is both the group and permission source: * The tables where you store your groups and users are the sections and the section items, respectively, of the ``group source``. * The tables where you store your permissions and groups are the sections and the section items, respectively, of the ``permission source``. .. _add-auth-middleware: Setting up authentication and authorization ------------------------------------------- .. note:: If you are using a web framework and it already configures :mod:`repoze.what`, you may want to skip this section. To enable authorization in your Web application, you need to add some WSGI middleware to your application, which is automatically done for you if you are using the :mod:`quickstart `. When you enable authorization with :mod:`repoze.what`, authentication with :mod:`repoze.who` is automatically enabled. .. warning:: Do not try to configure :mod:`repoze.who` directly -- if you want authorization to work, you have to configure it through :mod:`repoze.what`. Using authentication and authorization without the quickstart ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you're not using the quickstart, then you have to add the required middleware in your application. This gives you more flexibility, such as being able not to use a database to store your users' credentials, your groups and/or your permissions. You are highly encouraged to add such a middleware with a function defined in, say, ``{yourproject}.config.middleware`` and called, say, ``add_auth``. Then that function may look like this:: def add_auth(app): """ Add authentication and authorization middleware to the ``app``. :param app: The WSGI application. :return: The same WSGI application, with authentication and authorization middleware. People will login using HTTP Authentication and their credentials are kept in an ``Htpasswd`` file. For authorization through repoze.what, we load our groups stored in an ``Htgroups`` file and our permissions stored in an ``.ini`` file. """ from repoze.who.plugins.basicauth import BasicAuthPlugin from repoze.who.plugins.htpasswd import HTPasswdPlugin, crypt_check from repoze.what.middleware import setup_auth from repoze.what.plugins.ini import INIPermissionsAdapter # Please note that the Htgroups plugins has not been created yet; want # to jump in? from repoze.what.plugins.htgroups import HtgroupsAdapter # Defining the group adapters; you may add as much as you need: groups = {'all_groups': HtgroupsAdapter('/path/to/groups.htgroups')} # Defining the permission adapters; you may add as much as you need: permissions = {'all_perms': INIPermissionsAdapter('/path/to/perms.ini')} # repoze.who identifiers; you may add as much as you need: basicauth = BasicAuthPlugin('Private web site') identifiers = [('basicauth', basicauth)] # repoze.who authenticators; you may add as much as you need: htpasswd_auth = HTPasswdPlugin('/path/to/users.htpasswd', crypt_check) authenticators = [('htpasswd', htpasswd_auth)] # repoze.who challengers; you may add as much as you need: challengers = [('basicauth', basicauth)] app_with_auth = setup_auth( app, groups, permissions, identifiers=identifiers, authenticators=authenticators, challengers=challengers) return app_with_auth Of course, there are other things you may customize, such as adding :mod:`repoze.who` identifiers, more authenticators, challengers and metadata providers (read :func:`repoze.what.middleware.setup_auth` for more information). What's next? ------------ Now you are ready to control authorization in your application with :mod:`predicates `! repoze.what-1.0.9/docs/source/Manual/Predicates/0000755000175000017500000000000011356575761021501 5ustar gustavogustavorepoze.what-1.0.9/docs/source/Manual/Predicates/index.rst0000644000175000017500000000574511356575761023355 0ustar gustavogustavo********************************** Controlling access with predicates ********************************** .. topic:: Overview This document explains how to restrict access within your application by using :term:`predicates `, which you will be able to use once you have setup the required middleware, either through the quickstart or in a custom way. :mod:`repoze.what` allows you to define access rules based on so-called "predicate checkers": .. glossary:: predicate A ``predicate`` is the condition that must be met for the subject to be able to access the requested source (e.g., "The current user is not anonymous"). compound predicate A :term:`predicate`, or condition, may be made up of more predicates -- those are called `compound predicates` (e.g., "The user is not anonymous `and` her IP address belongs to the company's intranet"). predicate checker A class that checks whether a :term:`predicate` is met. It must extend :class:`repoze.what.predicates.Predicate`. If a user is not logged in, or does not have the proper permissions, the application throws a 401 (HTTP Unauthorized) which is caught by the :mod:`repoze.who` middleware to display the login page allowing the user to login, and redirecting the user back to the proper page when they are done. For example, if you have a predicate above ("The user is not anonymous"), then you can use the following built-in predicate checker:: from repoze.what.predicates import not_anonymous p = not_anonymous(msg='Only logged in users can read this post') Or if you have a predicate which is "The current user is root and/or somebody with the 'manage' permission", then you may use the following built-in predicate checkers:: from repoze.what.predicates import Any, is_user, has_permission p = Any(is_user('root'), has_permission('manage'), msg='Only administrators can remove blog posts') As you may have noticed, predicates receive the ``msg`` keyword argument to use its value as the error message if the predicate is not met. It's optional and if you don't define it, the built-in predicates will use the default English message; you may take advantage of this funtionality to make such messages translatable. .. note:: Good predicate messages don't explain `what` went wrong; instead, they describe the predicate in the current context (regardless of whether the condition is met or not!). This is because such messages may be used in places other than in a user-visible message (e.g., in the log file). * Really bad: "Please login to access this area". * Bad: "You cannot delete an user account because you are not an administrator". * OK: "You have to be an administrator to delete user accounts". * Perfect: "Only administrators can delete user accounts". More on predicates ================== .. toctree:: :maxdepth: 2 Builtin Evaluating Writing repoze.what-1.0.9/docs/source/Manual/Predicates/Builtin.rst0000644000175000017500000000270111356575761023641 0ustar gustavogustavo*************************** Built-in predicate checkers *************************** .. module:: repoze.what.predicates :synopsis: repoze.what predicates checkers These are the predicate checkers that are included with :mod:`repoze.what`: The base :class:`Predicate` class ================================= :class:`Predicate` is the parent class of every predicate checker and its API is described below: .. autoclass:: Predicate :members: __init__, evaluate, unmet, check_authorization, is_met, parse_variables, _eval_with_environ Single predicate checkers ========================= .. autoclass:: is_user .. autoclass:: not_anonymous .. autoclass:: is_anonymous .. autoclass:: in_group .. autoclass:: in_all_groups .. autoclass:: in_any_group .. autoclass:: has_permission .. autoclass:: has_all_permissions .. autoclass:: has_any_permission .. autoclass:: Not Compound predicate checkers =========================== You may create a `compound predicate` by aggregating single (or even compound) predicate checkers with the functions below: .. autoclass:: All .. autoclass:: Any But you can also nest compound predicates:: p = All(Any(is_month(4), is_month(10)), has_permission('release')) Which may be translated as "Anyone granted the 'release' permission may release a version of Ubuntu, if and only if it's April or October". Predicate errors ================ .. autoclass:: NotAuthorizedError .. autoclass:: PredicateError repoze.what-1.0.9/docs/source/Manual/Predicates/Writing.rst0000644000175000017500000001242411356575761023661 0ustar gustavogustavo******************************************* Creating your own single predicate checkers ******************************************* You may create your own predicate checkers if the built-in ones are not enough to achieve a given task. To do so, you should extend the :class:`repoze.what.predicates.Predicate` class. For example, if your predicate is "The current month is the specified one", your predicate checker may look like this:: from datetime import date from repoze.what.predicates import Predicate class is_month(Predicate): message = 'The current month must be %(right_month)s and it is ' \ '%(this_month)s' def __init__(self, right_month, **kwargs): self.right_month = right_month super(is_month, self).__init__(**kwargs) def evaluate(self, environ, credentials): # Let's calculate the current day on every evaluation because # the application may be running for many days; hence it's not # defined once in the constructor. today = date.today() if today.month != self.right_month: # Raise an exception because the predicate is not met. self.unmet(this_month=today.month) Then you can use your predicate this way:: # Grant access if the current month is March p = is_month(3) .. note:: When you create a predicate, don't try to guess/assume the context in which the predicate is evaluated when you write the predicate message because such a predicate may be used in a different context. * Bad: "The software can be released if it's %(right_month)s". * Good: "The current month must be %(right_month)s". Creating a predicate checker more sensitive to the request ---------------------------------------------------------- Authorization always depends on the context and :mod:`repoze.what` predicates are no exception. Access is controlled based on who the current user is, what groups she belongs to, what permissions she is granted, what her IP address is, what day is today and so on -- and such data are always provided by the context. The context is a wide term which doesn't only include information about *who* makes the request, but also about *what* is requested and *how* the request is made (represented by the WSGI environment), *when* it is requested and possibly include external conditions. With :mod:`repoze.what` predicates, you can control access based on any of the parts that make up the context (described above). However, this framework mostly helps you control access based on *who* the user is (her credentials), while gives you a hand to control access based on *what* is requested and *how* by passing the WSGI environ to the predicate checker (:meth:`Predicate.parse_variables ` exists to help you with the *what* too). Writing predicates based on *when* it is requested and external conditions (if any) is completely up to you. For example, if to allow users edit posts in a blog you don't only want the predicate "the current user is granted the ``edit-posts`` permission" (*who* makes the request) to be met, but also "the current user is the author of the post in question" (*what* is requested), you may write the latter as:: from repoze.what.predicates import Predicate # Say you use SQLAlchemy: from yourcoolapplication.model import BlogPost, DBSession class post_is_managed_by_author(Predicate): message = 'Only %(author)s can manage post %(post_id)s' def evaluate(self, environ, credentials): # Extracting the post Id from the GET variables vars = self.parse_variables(environ) post_id = vars['get'].get('post_id') # Loading the post object post = DBSession.query(BlogPost).get(post_id) # Checking if it's the author if post.author_userid != credentials.get('repoze.what.userid'): self.unmet(post_id=post_id, author=post.author_userid) If you don't use the :meth:`Predicate.parse_variables ` method, you would have to import and use `Paste `_'s :func:`paste.request.parse_querystring` and/or :func:`paste.request.parse_formvars` functions whenever authorization depends on *what* is requested. Finally, you would end up with the following compound predicates:: from repoze.what.predicates import All, has_permission # Can the user edit the post? p = All(has_permission('edit-post'), post_is_managed_by_author()) # Can the user delete the post? p2 = All(has_permission('delete-posts'), post_is_managed_by_author()) .. note:: If you're using a dispatcher like `Routes `_ or `Selector `_ and the variables you need are not passed in the query string nor as POST variables, you will find them in the dictionary returned by :meth:`Predicate.parse_variables `, either in the ``positional_args`` or ``named_args`` items -- check the `wsgiorg.routing_args specification `_ for more information. repoze.what-1.0.9/docs/source/Manual/Predicates/Evaluating.rst0000644000175000017500000001103111356575761024326 0ustar gustavogustavo************************** Evaluating your predicates ************************** Evaluating a predicate is to check whether it is met or not. There are two ways of evaluating predicates, and which one should you use depends on the situation: Raising an exception if the predicate is not met ------------------------------------------------ If you need to enforce that to access the requested resource the predicate must be met, you should use :meth:`Predicate.check_authorization ` because it will raise an exception if it's not met. For example, if you have a sensitive function that should be run by certain users, you may use it at the start of the function as in the example below:: # ... from repoze.what.predicates import has_permission # ... environ = give_me_the_wsgi_environ() # ... def add_comment(post_id, comment): has_permission('post-comment').check_authorization(environ) # If reached this point, then the user *can* leave a comment! new_comment = Comment(post=post_id, comment=comment) save(new_comment) The exception raised is :class:`repoze.what.predicates.NotAuthorizedError`. Web frameworks may provide utilities to make it easier to check authorization this way. For example, the TurboGears framework provides the ``@require`` decorator for actions, which can be used as in the example below:: # ... from tg import require # ... from repoze.what.predicates import has_permission # ... class BlogController(BaseController): # ... @expose('coolproject.templates.blog') @require(has_permission('post-comment')) def add_comment(self, post_id, comment): new_comment = Comment(post=post_id, comment=comment) save(new_comment) As you may have noticed, it's a more elegant solution because the predicate is defined outside of the method itself and the framework automatically passes the WSGI environment to :meth:`Predicate.check_authorization `. The framework also catches the exception and replaces it with a 401 HTTP error and a error message visible to the user. Finding if it's met or not -------------------------- If you want to control access on a portion of the resource, not in the whole resource, you may want to avoid the exception :meth:`Predicate.check_authorization ` would raise if the predicate that controls that portion is not met. For example, you may want to grant access to a given page if the user has the "post-comment" permission, but only display a polite message if she belongs to the "customer" group. This is where :meth:`Predicate.is_met ` would come into play. You can use it as in the code below:: # ... from repoze.what.predicates import has_permission, in_group # ... environ = give_me_the_wsgi_environ() # ... def add_comment(post_id, comment): has_permission('post-comment').check_authorization(environ) # If reached this point, then the user *can* leave a comment! new_comment = Comment(post=post_id, comment=comment) save(new_comment) if in_group('customer').is_met(environ): print_message('Dear customer, thanks for your comment!') :mod:`repoze.what.authorize` ============================ .. module:: repoze.what.authorize :synopsis: repoze.what authorization utilities Before :meth:`Predicate.check_authorization `, predicates were evaluated with :func:`check_authorization` where you wanted to restrict access. That function was run before performing the protected procedure so that it can raise the :class:`NotAuthorizedError ` exception if the user was not authorized: .. autofunction:: check_authorization For example, if you have a sensitive function that should be run by certain users, you may use it at the start of the function as in the example below:: # ... from repoze.what.authorize import check_authorization from repoze.what.predicates import has_permission # ... environ = give_me_the_wsgi_environ() # ... def add_comment(post_id, comment): check_authorization(has_permission('post-comment'), environ) # If reached this point, then the user *can* leave a comment! new_comment = Comment(post=post_id, comment=comment) save(new_comment) repoze.what-1.0.9/docs/source/Manual/InnerWorkings.rst0000644000175000017500000000512711356575761022754 0ustar gustavogustavo**************************************** The inner-workings of :mod:`repoze.what` **************************************** .. module:: repoze.what.middleware :synopsis: repoze.what WSGI middleware .. moduleauthor:: Gustavo Narea .. topic:: Overview :mod:`repoze.what` doesn't provide WSGI middleware per se. Instead, it configures and re-uses :mod:`repoze.who`'s. Middleware-related components are defined in the :mod:`repoze.what.middleware` module. It contains one function to configure :mod:`repoze.who` with support for :mod:`repoze.what` and the :mod:`repoze.who` metadata provider that loads authorization-related data in the :mod:`repoze.who` ``identity`` and the :mod:`repoze.what` ``credentials`` dictionaries. .. warning:: In :mod:`repoze.what` v2, the ``userid``, groups and permissions will only be loaded in the :mod:`repoze.what` ``credentials`` dictionary (``environ['repoze.what.credentials']``). So you are encouraged not to access this data from the :mod:`repoze.who` ``identity`` -- if you do so, you will have to update your code when you want to upgrade to v2. .. autofunction:: setup_auth WSGI environment variables ========================== :mod:`repoze.what` defines and uses the following WSGI environment variables: * ``repoze.what.credentials``: It contains authorization-related data about the current user (it's similar to :mod:`repoze.who`'s ``identity``). It is a dictionary made up of the following items: ``userid`` (the user name of the current user, if not anonymous; copied from ``environ['repoze.who.identity']['repoze.who.userid']`` in :mod:`repoze.what` v1.X), ``groups`` (tuple of groups to which the currrent user belongs) and ``permissions`` (tuple of permissions granted to such groups). .. warning:: Do **not** access this dictionary directly, use a :term:`predicate checker` instead. **This variable is internal** and the disposal or availability of its items may change at any time. * ``repoze.what.adapters``: It contains the available :term:`source adapters `, if any. It's a dictionary made up of the following items: ``groups`` (dictionary of :term:`group adapters `) and ``permissions`` (dictionary of :term:`permission adapters `). .. warning:: Because :mod:`repoze.what` 1.X works as a :mod:`repoze.who` metadata provider, the variables above are defined if and only if the current user is not anonymous. This limitation will not exist in :mod:`repoze.what` v2, since it will have its own middleware. repoze.what-1.0.9/docs/source/Manual/ManagingSources.rst0000644000175000017500000003114011356575761023234 0ustar gustavogustavo******************************************* How to manage groups and permission sources ******************************************* .. module:: repoze.what.adapters :synopsis: repoze.what source adapters .. moduleauthor:: Gustavo Narea .. topic:: Overview It's possible for you to manage your groups and permissions under a :term:`source`-independent API and here you will learn how to do it. :term:`Source adapters ` enable you to retrieve information from your :term:`groups ` and :term:`permissions `, as well as manage them, under an API absolutely independent of the source type. You may take advantage of this functionality to manage your sources from your own application or to write an application-independent front-end to manage groups and permissions in arbitrary WSGI applications using :mod:`repoze.what`. This functionality will also enable you to switch from one back-end to another with no need to update your code (except for the part where you instance the source adapter). Managing your sources ===================== Sources are managed from their respective adapters. For example, to manage the groups defined in a database, you can use:: from repoze.what.plugins.sql import SqlGroupsAdapter from your_model import User, Group, DBSession groups = SqlGroupsAdapter(Group, User, DBSession) Or to manage the permissions defined in an XML file, you could use:: from repoze.what.plugins.xml import XMLGroupsAdapter permissions = XMLGroupsAdapter('/path/to/permissions.xml') .. tip:: As of v1.0.1, you can re-use the same adapters used by :mod:`repoze.what` to control access. You will find them in the WSGI environment:: # This is where repoze.what adapters are kept: adapters = environ['repoze.what.adapters'] # Now let's extract the group and permission adapters: group_adapters = adapters['groups'] permission_adapters = adapters['permissions'] Retrieving all the available :term:`sections
` from a source --------------------------------------------------------------------- To get all the groups from the group source above, you may use the code below, which will return a dictionary whose keys are the name of the groups and the items are the username of the users that belong to such groups:: >>> groups.get_all_sections() {u'admins': set([u'gustavo', u'adolfo']), u'developers': set([u'narea'])} And to get all the permissions from the permission source above, you may use the code below, which will return a dictionary whose keys are the name of the permissions and the items are the name of the groups that are granted such permissions:: >>> permissions.get_all_sections() {u'upload-images': set([u'admins', u'developers']), u'write-post': set()} Retrieving all the :term:`items ` from a given :term:`section` -------------------------------------------------------------------- To get all the users that belong to a given group in the group source above, you may use:: >>> groups.get_section_items(u'admins') set([u'gustavo', u'adolfo']) And to get all the groups that are granted a given permission in the permission source above:: >>> permissions.get_section_items(u'upload-images') set([u'admins', u'developers']) Setting the :term:`items ` of a given :term:`section` ----------------------------------------------------------- To set the members of a given group in the group source above, you may use:: >>> groups.set_section_items(u'admins', [u'rms', u'guido']) And to set the groups that are granted a given permission in the permission source above:: >>> permissions.set_section_items(u'write-post', [u'admins']) .. warning:: ``set_section_items`` will `override` the previous set of items. See, for example:: >>> groups.get_all_sections() {u'admins': set([u'gustavo', u'adolfo']), u'developers': set([u'narea'])} >>> groups.set_section_items(u'admins', [u'rms', u'guido']) >>> groups.get_all_sections() {u'admins': set([u'rms', u'guido']), u'developers': set([u'narea'])} Including :term:`items ` in a :term:`section` --------------------------------------------------- To add one the item to a given group of the group source above, you may use:: >>> groups.include_item(u'admins', u'rms') Or to include many users at once:: >>> groups.include_items(u'admins', [u'rms', u'guido']) And to grant a given permission to one group in the permission source above:: >>> permissions.include_item(u'write-post', u'admins') Or to grant the same permission to many groups at once:: >>> permissions.include_items(u'write-post', [u'admins', u'developers']) Excluding :term:`items ` from a :term:`section` ----------------------------------------------------- To remove one the items from a given group of the group source above, you may use:: >>> groups.exclude_item(u'admins', u'gustavo') Or to exclude many items at once:: >>> groups.exclude_items(u'admins', [u'gustavo', u'adolfo']) And to deny a given permission to one group in the permission source above:: >>> permissions.exclude_item(u'upload-images', u'developers') Or to grant the same permission to many groups at once:: >>> permissions.exclude_items(u'upload-images', [u'admins', u'developers']) Adding a :term:`section` to a :term:`source` -------------------------------------------- To create a group in the group source above, you may use:: >>> groups.create_section(u'designers') And to create a permission in the permission source above:: >>> permissions.create_section(u'edit-post') Renaming a :term:`section` -------------------------- To rename a group in the group source above, you may use:: >>> groups.edit_section(u'designers', u'graphic-designers') And to rename a permission in the permission source above:: >>> permissions.edit_section(u'write-post', u'create-post') Removing a :term:`section` from a :term:`source` ------------------------------------------------ To remove a group from the group source above, you may use:: >>> groups.delete_section(u'developers') And to remove a permission from the permission source above:: >>> permissions.delete_section(u'write-post') Checking whether the :term:`source` is writable ----------------------------------------------- Some adapters may not support writting the source, or some source types may be read-only (e.g., a source served over HTTP), or some source types may be writable but the current source itself may be read-only (e.g., a read-only file). For this reason, you should check whether you can write to the source -- You will get a :class:`SourceError` exception if you try to write to a read-only source. To check whether the group source above is writable, you may use:: >>> groups.is_writable True And to check whether the permission source above is writable:: >>> permissions.is_writable False Possible problems ================= While dealing with an adapter, the following exceptions may be raised if an error occurs: .. autoexception:: AdapterError .. autoexception:: SourceError .. autoexception:: ExistingSectionError .. autoexception:: NonExistingSectionError .. autoexception:: ItemPresentError .. autoexception:: ItemNotPresentError Writing your own source adapters ================================ .. note:: It's `very` unlikely that you'll want to write a :term:`source adapter`, so if you get bored reading this section, it's absolutely safe for you to skip it and come back later if you `ever` need to create an adapter. Both :term:`group ` and :term:`permission ` :term:`adapters ` must extend the abstract class :class:`BaseSourceAdapter`: .. autoclass:: BaseSourceAdapter :members: __init__, _get_all_sections, _get_section_items, _find_sections, _include_items, _exclude_items, _item_is_included, _create_section, _edit_section, _delete_section, _section_exists Sample :term:`source adapters ` ----------------------------------------------- The following class illustrates how a :term:`group adapter` may look like:: from repoze.what.adapters import BaseSourceAdapter class FakeGroupSourceAdapter(BaseSourceAdapter): """Mock group source adapter""" def __init__(self, *args, **kwargs): super(FakeGroupSourceAdapter, self).__init__(*args, **kwargs) self.fake_sections = { u'admins': set([u'rms']), u'developers': set([u'rms', u'linus']), u'trolls': set([u'sballmer']), u'python': set(), u'php': set() } def _get_all_sections(self): return self.fake_sections def _get_section_items(self, section): return self.fake_sections[section] def _find_sections(self, credentials): username = credentials['repoze.what.userid'] return set([n for (n, g) in self.fake_sections.items() if username in g]) def _include_items(self, section, items): self.fake_sections[section] |= items def _exclude_items(self, section, items): for item in items: self.fake_sections[section].remove(item) def _item_is_included(self, section, item): return item in self.fake_sections[section] def _create_section(self, section): self.fake_sections[section] = set() def _edit_section(self, section, new_section): self.fake_sections[new_section] = self.fake_sections[section] del self.fake_sections[section] def _delete_section(self, section): del self.fake_sections[section] def _section_exists(self, section): return self.fake_sections.has_key(section) And the following class illustrates how a :term:`permission adapter` may look like:: from repoze.what.adapters import BaseSourceAdapter class FakePermissionSourceAdapter(BaseSourceAdapter): """Mock permissions source adapter""" def __init__(self, *args, **kwargs): super(FakePermissionSourceAdapter, self).__init__(*args, **kwargs) self.fake_sections = { u'see-site': set([u'trolls']), u'edit-site': set([u'admins', u'developers']), u'commit': set([u'developers']) } def _get_all_sections(self): return self.fake_sections def _get_section_items(self, section): return self.fake_sections[section] def _find_sections(self, group_name): return set([n for (n, p) in self.fake_sections.items() if group_name in p]) def _include_items(self, section, items): self.fake_sections[section] |= items def _exclude_items(self, section, items): for item in items: self.fake_sections[section].remove(item) def _item_is_included(self, section, item): return item in self.fake_sections[section] def _create_section(self, section): self.fake_sections[section] = set() def _edit_section(self, section, new_section): self.fake_sections[new_section] = self.fake_sections[section] del self.fake_sections[section] def _delete_section(self, section): del self.fake_sections[section] def _section_exists(self, section): return self.fake_sections.has_key(section) Testing your source adapters with :mod:`testutil ` --------------------------------------------------------------------------------- .. module:: repoze.what.adapters.testutil :synopsis: Automatic tests for repoze.what source adapters :mod:`repoze.what` provides convenient utilities to automate the verification of your adapters. This utility is the :mod:`repoze.what.adapters.testutil` module, made up four test cases, which when extended must define the adapter (as ``self.adapter``) in the setup, as well as call this class' ``setUp()`` method: .. autoclass:: ReadOnlyGroupsAdapterTester .. autoclass:: ReadOnlyPermissionsAdapterTester .. autoclass:: GroupsAdapterTester .. autoclass:: PermissionsAdapterTester .. attention:: :mod:`repoze.what.adapters.testutil` is not a full replacement for a test suite, so you are still highly encouraged to write the relevant/missing tests to lead the code coverage of your adapters to 100%. repoze.what-1.0.9/docs/source/_static/0000755000175000017500000000000011356575761017627 5ustar gustavogustavorepoze.what-1.0.9/docs/source/_static/logo_hi.gif0000644000175000017500000000777611356575761021757 0ustar gustavogustavoGIF89a4polܵEEE999VVUŪ؉ǼʤჂ򠞙dcaRRQ]\\{zx???MLL444333!,4@pH,Ȥrl:Ш@Zجvzxl-z|~su2WfoTe`UXboef :7=Óʋ<Ϥ5)ݳ߼˿ڶܾՎ΢55=C!ѷm+M>o6dtu._ݘUn>؟3:XfC`[0/h uCP![ W d(]"Xr_ 9"#2dwHe.zآ0]Z}7L PeI8tO aiCÃ WI -*蠄ozXv@5 @d "^0e80ݪ&p"|Cnk, 06,w)>jjwiɂ< ɂ:p +[8th k(8,#h,,/U"CPCg Z6 滲˙2宐A:Ё|{J/ B ؁Pp ZP p MK C,( g l| !p'| T)Prª P t5:z|pBX*R3( D-,. A'G, @8 @A*H@>P ql+0 䯀t)T>P`&(0,=#@V`?-;h_S܃ŭqWuo @/S^&  gZUG >B^EZUmyUk-}T0]w~;ܙ9%pquxO0 h07]- K-[^`q< ]rCyPb6Gy$a>p(%%@$g- }ǀ;%y'?rgy<2Tk=>Ng~'@S2D1\4l#px(w\i׀eBxx42-%eT\?05Ph>(}`(&u$`x/"r(^@6†Vz#ehy)iVhKng(`NHz!wB.  gx%d؉mȆl\e-7p_}4z!<`P#K`h~v,0\e `74(;0H9Ȇ<8،<@1#@։T9l(؎8mȆ $ LC9'J(.`KP hs3`"#׎yS:Y: 92`X`Yh ن0i2i 0m20UTYX`FC3Xo:HXyLB7. QI׋ 0dYfy)B gpr9tYtȗ~)|Xr(RV؄ 2xMxu2yuWɈZə捔ق闫W)B8^hBy)iXyiZHz¨I py1|?P雿Iٙy Ti PTK#p:ZP`a)zzEɚ  p?2UĢâ-j-ꓮB2/:1:= pq)@9>)U.ZFKKʣ>:0ڢ,60%ʥ#$b1P3Хɥ]٦Ӷ>ijkm 0 `U+mJ  l*djjr*A7ॆj v DGiZ$b905- :J** jJ*KfK jm2x=ښrʬJšjZ ׮*:ʩk8گʮ#Gڊ{7P0hzʮ:zK = 4 y<2=7U, M9P1:˳> +@+;KE۳@;LۮO۴NS[L[P;\XO+W;efac[\K]kR Y;repoze.what-1.0.9/docs/source/_static/repoze.css0000644000175000017500000000047411356575761021652 0ustar gustavogustavo@import url('default.css'); body { background-color: #006339; } div.document { background-color: #dad3bd; } div.sphinxsidebar h3,h4,h5,li,a { color: #127c56 !important; } div.related { color: #dad3bd !important; background-color: #00744a; } div.related a { color: #dad3bd !important; } repoze.what-1.0.9/PKG-INFO0000644000175000017500000000433211356577163015046 0ustar gustavogustavoMetadata-Version: 1.0 Name: repoze.what Version: 1.0.9 Summary: Authorization framework for WSGI applications Home-page: http://static.repoze.org/whatdocs/ Author: Gustavo Narea Author-email: repoze-dev@lists.repoze.org License: BSD-derived (http://www.repoze.org/LICENSE.txt) Description: ************************************************** repoze.what -- Authorization for WSGI applications ************************************************** :mod:`repoze.what` is an `authorization framework` for WSGI applications, based on :mod:`repoze.who` (which deals with `authentication` and `identification`). On the one hand, it enables an authorization system based on the groups to which the `authenticated or anonymous` user belongs and the permissions granted to such groups by loading these groups and permissions into the request on the way in to the downstream WSGI application. And on the other hand, it enables you to manage your groups and permissions from the application itself or another program, under a backend-independent API. For example, it would be easy for you to switch from one back-end to another, and even use this framework to migrate the data. This is just the authorization pattern it supports out-of-the-box, but you can may it support other authorization patterns with your own predicates. It's highly extensible, so it's very unlikely that it will get in your way -- Among other things, you can extend it to check for many conditions (such as checking that the user comes from a given country, based on her IP address, for example). Keywords: authorization web application server wsgi repoze Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Security Classifier: Topic :: Software Development :: Libraries :: Application Frameworks repoze.what-1.0.9/functional-testing.cfg0000644000175000017500000000026711356575761020254 0ustar gustavogustavo# Nose configuration file for functional tests [nosetests] where = functional_tests verbose = 1 verbosity = 1 detailed-errors = 1 no-path-adjustment = 1 testmatch = ^(tests|test_.*)$ repoze.what-1.0.9/setup.cfg0000644000175000017500000000053411356577163015572 0ustar gustavogustavo[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [nosetests] cover-erase = 1 nocapture = 1 verbose = 1 cover-package = repoze.what verbosity = 1 with-coverage = 1 detailed-errors = 1 no-path-adjustment = 1 testmatch = ^(tests|test_.*)$ with-doctest = 1 where = tests [aliases] release = egg_info -rDb "" sdist bdist_egg register upload repoze.what-1.0.9/repoze.what.egg-info/0000755000175000017500000000000011356577163017707 5ustar gustavogustavorepoze.what-1.0.9/repoze.what.egg-info/requires.txt0000644000175000017500000000010311356577163022301 0ustar gustavogustavorepoze.who >= 1.0, <= 1.99 repoze.who-testutil >= 1.0b2 Paste > 1.7repoze.what-1.0.9/repoze.what.egg-info/SOURCES.txt0000644000175000017500000000253311356577163021576 0ustar gustavogustavoREADME.txt VERSION.txt ez_setup.py functional-testing.cfg setup.cfg setup.py docs/Makefile docs/source/News.rst docs/source/Participate.rst docs/source/conf.py docs/source/index.rst docs/source/Manual/GettingStarted.rst docs/source/Manual/InnerWorkings.rst docs/source/Manual/ManagingSources.rst docs/source/Manual/index.rst docs/source/Manual/Plugins/index.rst docs/source/Manual/Predicates/Builtin.rst docs/source/Manual/Predicates/Evaluating.rst docs/source/Manual/Predicates/Writing.rst docs/source/Manual/Predicates/index.rst docs/source/_static/logo_hi.gif docs/source/_static/repoze.css functional_tests/__init__.py functional_tests/test_threading.py repoze/__init__.py repoze.what.egg-info/PKG-INFO repoze.what.egg-info/SOURCES.txt repoze.what.egg-info/dependency_links.txt repoze.what.egg-info/entry_points.txt repoze.what.egg-info/namespace_packages.txt repoze.what.egg-info/not-zip-safe repoze.what.egg-info/requires.txt repoze.what.egg-info/top_level.txt repoze/what/__init__.py repoze/what/authorize.py repoze/what/middleware.py repoze/what/predicates.py repoze/what/release.py repoze/what/adapters/__init__.py repoze/what/adapters/testutil.py repoze/what/plugins/__init__.py tests/__init__.py tests/base.py tests/test_adapters.py tests/test_adapters_testutil.py tests/test_authorize.py tests/test_middleware.py tests/test_predicates.py tests/test_release.pyrepoze.what-1.0.9/repoze.what.egg-info/PKG-INFO0000644000175000017500000000433211356577163021006 0ustar gustavogustavoMetadata-Version: 1.0 Name: repoze.what Version: 1.0.9 Summary: Authorization framework for WSGI applications Home-page: http://static.repoze.org/whatdocs/ Author: Gustavo Narea Author-email: repoze-dev@lists.repoze.org License: BSD-derived (http://www.repoze.org/LICENSE.txt) Description: ************************************************** repoze.what -- Authorization for WSGI applications ************************************************** :mod:`repoze.what` is an `authorization framework` for WSGI applications, based on :mod:`repoze.who` (which deals with `authentication` and `identification`). On the one hand, it enables an authorization system based on the groups to which the `authenticated or anonymous` user belongs and the permissions granted to such groups by loading these groups and permissions into the request on the way in to the downstream WSGI application. And on the other hand, it enables you to manage your groups and permissions from the application itself or another program, under a backend-independent API. For example, it would be easy for you to switch from one back-end to another, and even use this framework to migrate the data. This is just the authorization pattern it supports out-of-the-box, but you can may it support other authorization patterns with your own predicates. It's highly extensible, so it's very unlikely that it will get in your way -- Among other things, you can extend it to check for many conditions (such as checking that the user comes from a given country, based on her IP address, for example). Keywords: authorization web application server wsgi repoze Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Security Classifier: Topic :: Software Development :: Libraries :: Application Frameworks repoze.what-1.0.9/repoze.what.egg-info/entry_points.txt0000644000175000017500000000000611356577163023201 0ustar gustavogustavo repoze.what-1.0.9/repoze.what.egg-info/dependency_links.txt0000644000175000017500000000000111356577163023755 0ustar gustavogustavo repoze.what-1.0.9/repoze.what.egg-info/namespace_packages.txt0000644000175000017500000000004711356577163024243 0ustar gustavogustavorepoze repoze.what repoze.what.plugins repoze.what-1.0.9/repoze.what.egg-info/top_level.txt0000644000175000017500000000000711356577163022436 0ustar gustavogustavorepoze repoze.what-1.0.9/repoze.what.egg-info/not-zip-safe0000644000175000017500000000000111356576101022124 0ustar gustavogustavo repoze.what-1.0.9/repoze/0000755000175000017500000000000011356575761015255 5ustar gustavogustavorepoze.what-1.0.9/repoze/__init__.py0000644000175000017500000000204211356575761017364 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages try: __import__('pkg_resources').declare_namespace(__name__) except ImportError: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) repoze.what-1.0.9/repoze/what/0000755000175000017500000000000011356575761016220 5ustar gustavogustavorepoze.what-1.0.9/repoze/what/release.py0000644000175000017500000000227011356575761020213 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2008-2009, Gustavo Narea # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ repoze.what release information. The version number is loaded to help the Quickstart plugin configure repoze.what correctly, depending on the version available -- although it may be useful on other packages. """ import os _here = os.path.abspath(os.path.dirname(__file__)) _root = os.path.dirname(os.path.dirname(_here)) version = open(os.path.join(_root, 'VERSION.txt')).readline().rstrip() # The major version: If version=='3.0.2rc4', the major version is int(3). major_version = int(version.split('.')[0]) repoze.what-1.0.9/repoze/what/adapters/0000755000175000017500000000000011356575761020023 5ustar gustavogustavorepoze.what-1.0.9/repoze/what/adapters/testutil.py0000644000175000017500000002717311356575761022264 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """Utilities to test source adapters.""" from repoze.what.adapters import SourceError, ExistingSectionError, \ NonExistingSectionError, \ ItemPresentError, ItemNotPresentError __all__ = ['GroupsAdapterTester', 'PermissionsAdapterTester', 'ReadOnlyGroupsAdapterTester', 'ReadOnlyPermissionsAdapterTester'] class _ReadOnlyBaseAdapterTester(object): """Base test case for read-only adapters""" def _get_all_items(self): all_items = set() for items in self.all_sections.values(): all_items |= items return all_items def _get_item_sections(self, item): return set([n for (n, s) in self.all_sections.items() if item in s]) def test_retrieving_all_sections(self): self.assertEqual(self.adapter._get_all_sections(), self.all_sections) def test_getting_section_items(self): for section_name, items in self.all_sections.items(): self.assertEqual(self.adapter._get_section_items(section_name), items) def test_checking_existing_section(self): for section_name in self.all_sections.keys(): assert self.adapter._section_exists(section_name), \ 'Section "%s" does NOT exist' % section_name def test_checking_non_existing_section(self): section_name = u'i_dont_exist' assert not self.adapter._section_exists(section_name), \ 'Section "%s" DOES exist' % section_name def test_checking_item_inclusion(self): for section_name, items in self.all_sections.items(): for item in self.adapter._get_section_items(section_name): assert self.adapter._item_is_included(section_name, item), \ 'Item "%s" must be included in section "%s"' % \ (item, section_name) def test_checking_excluded_item_inclusion(self): excluded_item = self.new_items.pop() for section_name, items in self.all_sections.items(): assert not self.adapter._item_is_included(section_name, excluded_item), \ 'Item "%s" must not included in section "%s"' % \ (item, section_name) def test_checking_section_existence(self): for section_name in self.all_sections.keys(): assert self.adapter._section_exists(section_name), \ 'Section "%s" must exist' % section_name def test_checking_non_existing_section_existence(self): invalid_section = u'designers' assert not self.adapter._section_exists(invalid_section), \ 'Section "%s" must not exist' % invalid_section def test_sets_if_it_is_writable(self): assert hasattr(self.adapter, 'is_writable'), \ "The adapter doesn't have the 'is_writable' attribute; " \ "please call its parent's constructor too" class _BaseAdapterTester(_ReadOnlyBaseAdapterTester): """Base test case for read & write adapters""" def test_adding_many_items_to_section(self): for section_name, items in self.all_sections.items(): self.adapter._include_items(section_name, self.new_items) final_items = items | self.new_items assert self.adapter._get_section_items(section_name)==final_items, \ '"%s" does not include %s' % (section_name, self.new_items) def test_creating_section(self): section = u'cool-section' self.adapter._create_section(section) assert section in self.adapter._get_all_sections().keys(), \ 'Section "%s" could not be added' % section def test_editing_section(self): old_section = self.all_sections.keys()[0] new_section = u'cool-section' self.adapter._edit_section(old_section, new_section) assert new_section in self.adapter._get_all_sections().keys() and \ old_section not in self.adapter._get_all_sections().keys(), \ 'Section "%s" was not renamed to "%s"' % (old_section, new_section) def test_deleting_section(self): section = self.all_sections.keys()[0] self.adapter._delete_section(section) assert section not in self.adapter._get_all_sections().keys(), \ 'Section "%s" was not deleted' % section class ReadOnlyGroupsAdapterTester(_ReadOnlyBaseAdapterTester): """ Test case for read-only groups source adapters. The groups source used for the tests must only contain the following groups (aka "sections") and their relevant users (aka "items"; if any): * admins * rms * developers * rms * linus * trolls * sballmer * python * php .. attribute:: adapter An instance of the :term:`group adapter` to be tested. For example, a test case for the mock group adapter ``FakeReadOnlyGroupSourceAdapter`` may look like this:: from repoze.what.adapters.testutil import ReadOnlyGroupsAdapterTester class TestReadOnlyGroupsAdapterTester(ReadOnlyGroupsAdapterTester, unittest.TestCase): def setUp(self): super(TestReadOnlyGroupsAdapterTester, self).setUp() self.adapter = FakeReadOnlyGroupSourceAdapter() .. note:: :class:`GroupsAdapterTester` extends this test case to check write operations. """ new_items = set((u'guido', u'rasmus')) def setUp(self): self.all_sections = { u'admins': set((u'rms', )), u'developers': set((u'rms', u'linus')), u'trolls': set((u'sballmer', )), u'python': set(), u'php': set() } def _make_credentials(self, userid): """ Return a fake :mod:`repoze.what` ``credentials`` dictionary based on the ``userid``. Overwrite this method if its generated ``credentials`` dictionaries are not suitable for your adapter. """ return {'repoze.what.userid': userid} def test_finding_groups_of_authenticated_user(self): for userid in self._get_all_items(): credentials = self._make_credentials(userid) self.assertEqual(self.adapter._find_sections(credentials), self._get_item_sections(userid)) def test_finding_groups_of_non_existing_user(self): credentials = self._make_credentials(u'gustavo') self.assertEqual(self.adapter._find_sections(credentials), set()) class GroupsAdapterTester(ReadOnlyGroupsAdapterTester, _BaseAdapterTester): """ Test case for groups source adapters. This test case extends :class:`ReadOnlyGroupsAdapterTester` to test write operations in read & write adapters and it should be set up the same way as its parent. For example, a test case for the mock group adapter ``FakeGroupSourceAdapter`` may look like this:: from repoze.what.adapters.testutil import GroupsAdapterTester class TestGroupsAdapterTester(GroupsAdapterTester, unittest.TestCase): def setUp(self): super(TestGroupsAdapterTester, self).setUp() self.adapter = FakeGroupSourceAdapter() """ def test_removing_many_users_from_group(self): group = u'developers' users = (u'rms', u'linus') self.adapter._exclude_items(group, users) assert self.adapter._get_section_items(group)==set(), \ '"%s" still includes %s' % (group, users) class ReadOnlyPermissionsAdapterTester(_ReadOnlyBaseAdapterTester): """ Test case for read-only permissions source adapters. The permissions source used for the tests must only contain the following permissions (aka "sections") and their relevant groups (aka "items"; if any): * see-site * trolls * edit-site * admins * developers * commit * developers .. attribute:: adapter An instance of the :term:`permission adapter` to be tested. For example, a test case for the mock permission adapter defined above (``FakeReadOnlyPermissionSourceAdapter``) may look like this:: from repoze.what.adapters.testutil import ReadOnlyPermissionsAdapterTester class TestReadOnlyPermissionsAdapterTester(ReadOnlyPermissionsAdapterTester, unittest.TestCase): def setUp(self): super(TestReadOnlyPermissionsAdapterTester, self).setUp() self.adapter = FakeReadOnlyPermissionSourceAdapter() .. note:: :class:`PermissionsAdapterTester` extends this test case to check write operations. """ new_items = set((u'python', u'php')) def setUp(self): self.all_sections = { u'see-site': set((u'trolls', )), u'edit-site': set((u'admins', u'developers')), u'commit': set((u'developers', )) } def test_finding_permissions(self): for group in self._get_all_items(): self.assertEqual(self.adapter._find_sections(group), self._get_item_sections(group)) def test_finding_permissions_of_non_existing_group(self): self.assertEqual(self.adapter._find_sections(u'designers'), set()) class PermissionsAdapterTester(ReadOnlyPermissionsAdapterTester, _BaseAdapterTester): """ Test case for permissions source adapters. This test case extends :class:`ReadOnlyPermissionsAdapterTester` to test write operations in read & write adapters and it should be set up the same way as its parent. For example, a test case for the mock group adapter ``FakePermissionSourceAdapter`` may look like this: For example, a test case for the mock permission adapter defined above (``FakePermissionSourceAdapter``) may look like this:: from repoze.what.adapters.testutil import PermissionsAdapterTester class TestPermissionsAdapterTester(PermissionsAdapterTester, unittest.TestCase): def setUp(self): super(TestPermissionsAdapterTester, self).setUp() self.adapter = FakePermissionSourceAdapter() """ def test_deying_permisssion_to_many_groups(self): permission = u'edit-site' groups = (u'admins', u'developers') self.adapter._exclude_items(permission, groups) assert self.adapter._get_section_items(permission)==set(), \ '"%s" still includes %s' % (permission, groups) repoze.what-1.0.9/repoze/what/adapters/__init__.py0000644000175000017500000005463511356575761022151 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Base adapters for the supported source types. This is, the foundations for the source adapters defined in plugins. In a ``group source adapter``, ``a section is a group`` in the source and ``its items are the users that belong to that group``. Example: If Bob and Mary belong to the "developers" group, then you can also say that items Bob and Mary belong to the "developers" section. In a ``permission source adapter``, ``a section is a permission`` in the source and ``its items are the groups that are granted that permission``. Example: If "developers" and "designers" are granted the right to update the web site ("update-site"), then you can also say that items "developers" and "designers" belong to the "update-site" section. @todo: Add support for "universal sections" (those containing item "_"). @todo: Add support for "anonymous sections" (those containing item "-"). """ from zope.interface import Interface __all__ = ['BaseSourceAdapter', 'AdapterError', 'SourceError', 'ExistingSectionError', 'NonExistingSectionError', 'ItemPresentError', 'ItemNotPresentError'] class BaseSourceAdapter(object): """ Base class for :term:`source adapters `. Please note that these abstract methods may only raise one exception: :class:`SourceError`, which is raised if there was a problem while dealing with the source. They may not raise other exceptions because they should not validate anything but the source (not even the parameters they get). .. attribute:: is_writable = True :type: bool Whether the adapter can write to the source. If the source type handled by your adapter doesn't support write access, or if your adapter itself doesn't support writting to the source (yet), then you should set this value to ``False`` in the class itself; it will get overriden if the ``writable`` parameter in :meth:`the contructor ` is set, unless you explicitly disable that parameter:: # ... class MyFakeAdapter(BaseSourceAdapter): def __init__(): super(MyFakeAdapter, self).__init__(writable=False) # ... .. note:: If it's ``False``, then you don't have to define the methods that modify the source because they won't be used: * :meth:`_include_items` * :meth:`_exclude_items` * :meth:`_create_section` * :meth:`_edit_section` * :meth:`_delete_section` .. warning:: Do not ever cache the results -- that is :class:`BaseSourceAdapter`'s job. It requests a given datum once, not multiple times, thanks to its internal cache. """ def __init__(self, writable=True): """ Run common setup for source adapters. :param writable: Whether the source is writable. :type writable: bool """ # The cache for the sections loaded by the source adapter. self.loaded_sections = {} # Whether all of the existing items have been loaded self.all_sections_loaded = False # Whether the current source is writable: self.is_writable = writable def get_all_sections(self): """ Return all the sections found in the source. :return: All the sections found in the source. :rtype: dict :raise SourceError: If there was a problem with the source. """ if not self.all_sections_loaded: self.loaded_sections = self._get_all_sections() self.all_sections_loaded = True return self.loaded_sections def get_section_items(self, section): """ Return the properties of ``section``. :param section: The name of the section to be fetched. :type section: unicode :return: The items of the ``section``. :rtype: tuple :raise NonExistingSectionError: If the requested section doesn't exist. :raise SourceError: If there was a problem with the source. """ if section not in self.loaded_sections: self._check_section_existence(section) # It does exist; let's load it: self.loaded_sections[section] = self._get_section_items(section) return self.loaded_sections[section] def set_section_items(self, section, items): """ Set ``items`` as the only items of the ``section``. :raise NonExistingSectionError: If the section doesn't exist. :raise SourceError: If there was a problem with the source. """ old_items = self.get_section_items(section) items = set(items) # Finding what was added and what was removed: added = set((i for i in items if i not in old_items)) removed = set((i for i in old_items if i not in items)) # Removing/adding as requested. We're removing first to avoid # increasing the size of the source more than required. self.exclude_items(section, removed) self.include_items(section, added) # The cache must have been updated by the two methods above. def find_sections(self, hint): """ Return the sections that meet a given criteria. :param hint: repoze.what's credentials dictionary or a group name. :type hint: dict or unicode :return: The sections that meet the criteria. :rtype: tuple :raise SourceError: If there was a problem with the source. """ return self._find_sections(hint) def include_item(self, section, item): """ Include ``item`` in ``section``. This is the individual (non-bulk) edition of :meth:`include_items`. :param section: The ``section`` to contain the ``item``. :type section: unicode :param item: The new ``item`` of the ``section``. :type item: tuple :raise NonExistingSectionError: If the ``section`` doesn't exist. :raise ItemPresentError: If the ``item`` is already included. :raise SourceError: If there was a problem with the source. """ self.include_items(section, (item, )) def include_items(self, section, items): """ Include ``items`` in ``section``. This is the bulk edition of :meth:`include_item`. :param section: The ``section`` to contain the ``items``. :type section: unicode :param items: The new ``items`` of the ``section``. :type items: tuple :raise NonExistingSectionError: If the ``section`` doesn't exist. :raise ItemPresentError: If at least one of the items is already present. :raise SourceError: If there was a problem with the source. """ # Verifying that the section exists and doesn't already contain the # items: self._check_section_existence(section) for i in items: self._confirm_item_not_present(section, i) # Verifying write permissions: self._check_writable() # Everything's OK, let's add it: items = set(items) self._include_items(section, items) # Updating the cache, if necessary: if section in self.loaded_sections: self.loaded_sections[section] |= items def exclude_item(self, section, item): """ Exclude ``item`` from ``section``. This is the individual (non-bulk) edition of :meth:`exclude_items`. :param section: The ``section`` that contains the ``item``. :type section: unicode :param item: The ``item`` to be removed from ``section``. :type item: tuple :raise NonExistingSectionError: If the ``section`` doesn't exist. :raise ItemNotPresentError: If the item is not included in the section. :raise SourceError: If there was a problem with the source. """ self.exclude_items(section, (item, )) def exclude_items(self, section, items): """ Exclude items from section. This is the bulk edition of :meth:`exclude_item`. :param section: The ``section`` that contains the ``items``. :type section: unicode :param items: The ``items`` to be removed from ``section``. :type items: tuple :raise NonExistingSectionError: If the ``section`` doesn't exist. :raise ItemNotPresentError: If at least one of the items is not included in the section. :raise SourceError: If there was a problem with the source. """ # Verifying that the section exists and already contains the items: self._check_section_existence(section) for i in items: self._confirm_item_is_present(section, i) # Verifying write permissions: self._check_writable() # Everything's OK, let's remove them: items = set(items) self._exclude_items(section, items) # Updating the cache, if necessary: if section in self.loaded_sections: self.loaded_sections[section] -= items def create_section(self, section): """ Add ``section`` to the source. :param section: The section name. :type section: unicode :raise ExistingSectionError: If the section name is already in use. :raise SourceError: If there was a problem with the source. """ self._check_section_not_existence(section) self._check_writable() self._create_section(section) # Adding to the cache: self.loaded_sections[section] = set() def edit_section(self, section, new_section): """ Edit ``section``'s properties. :param section: The current name of the section. :type section: unicode :param new_section: The new name of the section. :type new_section: unicode :raise NonExistingSectionError: If the section doesn't exist. :raise SourceError: If there was a problem with the source. """ self._check_section_existence(section) self._check_writable() self._edit_section(section, new_section) # Updating the cache too, if loaded: if section in self.loaded_sections: self.loaded_sections[new_section] = self.loaded_sections[section] del self.loaded_sections[section] def delete_section(self, section): """ Delete ``section``. It removes the ``section`` from the source. :param section: The name of the section to be deleted. :type section: unicode :raise NonExistingSectionError: If the section in question doesn't exist. :raise SourceError: If there was a problem with the source. """ self._check_section_existence(section) self._check_writable() self._delete_section(section) # Removing from the cache too, if loaded: if section in self.loaded_sections: del self.loaded_sections[section] def _check_writable(self): """ Raise an exception if the source is not writable. :raise SourceError: If the source is not writable. """ if not self.is_writable: raise SourceError('The source is not writable') def _check_section_existence(self, section): """ Raise an exception if ``section`` is not defined in the source. :param section: The name of the section to look for. :type section: unicode :raise NonExistingSectionError: If the section is not defined. :raise SourceError: If there was a problem with the source. """ if not self._section_exists(section): msg = u'Section "%s" is not defined in the source' % section raise NonExistingSectionError(msg) def _check_section_not_existence(self, section): """ Raise an exception if ``section`` is defined in the source. :param section: The name of the section to look for. :type section: unicode :raise ExistingSectionError: If the section is defined. :raise SourceError: If there was a problem with the source. """ if self._section_exists(section): msg = u'Section "%s" is already defined in the source' % section raise ExistingSectionError(msg) def _confirm_item_is_present(self, section, item): """ Raise an exception if ``section`` doesn't contain ``item``. :param section: The name of the section that may contain the item. :type section: unicode :param item: The name of the item to look for. :type item: unicode :raise NonExistingSectionError: If the section doesn't exist. :raise ItemNotPresentError: If the item is not included. :raise SourceError: If there was a problem with the source. """ self._check_section_existence(section) if not self._item_is_included(section, item): msg = u'Item "%s" is not defined in section "%s"' % (item, section) raise ItemNotPresentError(msg) def _confirm_item_not_present(self, section, item): """ Raise an exception if ``section`` already contains ``item``. :param section: The name of the section that may contain the item. :type section: unicode :param item: The name of the item to look for. :type item: unicode :raise NonExistingSectionError: If the section doesn't exist. :raise ItemPresentError: If the item is already included. :raise SourceError: If there was a problem with the source. """ self._check_section_existence(section) if self._item_is_included(section, item): msg = u'Item "%s" is already defined in section "%s"' % (item, section) raise ItemPresentError(msg) #{ Abstract methods def _get_all_sections(self): """ Return all the sections found in the source. :return: All the sections found in the source. :rtype: dict :raise SourceError: If there was a problem with the source while retrieving the sections. """ raise NotImplementedError() def _get_section_items(self, section): """ Return the items of the section called ``section``. :param section: The name of the section to be fetched. :type section: unicode :return: The items of the section. :rtype: set :raise SourceError: If there was a problem with the source while retrieving the section. .. attention:: When implementing this method, don't check whether the section really exists; that's already done when this method is called. """ raise NotImplementedError() def _find_sections(self, hint): """ Return the sections that meet a given criteria. :param hint: repoze.what's credentials dictionary or a group name. :type hint: dict or unicode :return: The sections that meet the criteria. :rtype: tuple :raise SourceError: If there was a problem with the source while retrieving the sections. This method depends on the type of adapter that is implementing it: * If it's a ``group`` source adapter, it returns the groups the authenticated user belongs to. In this case, hint represents repoze.what's credentials dict. Please note that hint is not an user name because some adapters may need something else to find the groups the authenticated user belongs to. For example, LDAP adapters need the full Distinguished Name (DN) in the credentials dict, or a given adapter may only need the email address, so the user name alone would be useless in both situations. * If it's a ``permission`` source adapter, it returns the name of the permissions granted to the group in question; here hint represents the name of such a group. """ raise NotImplementedError() def _include_items(self, section, items): """ Add ``items`` to the ``section``, in the source. :param section: The section to contain the items. :type section: unicode :param items: The new items of the section. :type items: tuple :raise SourceError: If the items could not be added to the section. .. attention:: When implementing this method, don't check whether the section really exists or the items are already included; that's already done when this method is called. """ raise NotImplementedError() def _exclude_items(self, section, items): """ Remove ``items`` from the ``section``, in the source. :param section: The section that contains the items. :type section: unicode :param items: The items to be removed from section. :type items: tuple :raise SourceError: If the items could not be removed from the section. .. attention:: When implementing this method, don't check whether the section really exists or the items are already included; that's already done when this method is called. """ raise NotImplementedError() def _item_is_included(self, section, item): """ Check whether ``item`` is included in ``section``. :param section: The name of the item to look for. :type section: unicode :param section: The name of the section that may include the item. :type section: unicode :return: Whether the item is included in section or not. :rtype: bool :raise SourceError: If there was a problem with the source. .. attention:: When implementing this method, don't check whether the section really exists; that's already done when this method is called. """ raise NotImplementedError() def _create_section(self, section): """ Add ``section`` to the source. :param section: The section name. :type section: unicode :raise SourceError: If the section could not be added. .. attention:: When implementing this method, don't check whether the section already exists; that's already done when this method is called. """ raise NotImplementedError() def _edit_section(self, section, new_section): """ Edit ``section``'s properties. :param section: The current name of the section. :type section: unicode :param new_section: The new name of the section. :type new_section: unicode :raise SourceError: If the section could not be edited. .. attention:: When implementing this method, don't check whether the section really exists; that's already done when this method is called. """ raise NotImplementedError() def _delete_section(self, section): """ Delete ``section``. It removes the ``section`` from the source. :param section: The name of the section to be deleted. :type section: unicode :raise SourceError: If the section could not be deleted. .. attention:: When implementing this method, don't check whether the section really exists; that's already done when this method is called. """ raise NotImplementedError() def _section_exists(self, section): """ Check whether ``section`` is defined in the source. :param section: The name of the section to check. :type section: unicode :return: Whether the section is the defined in the source or not. :rtype: bool :raise SourceError: If there was a problem with the source. """ raise NotImplementedError() #} #{ Exceptions class AdapterError(Exception): """ Base exception for problems in the source adapters. It's never raised directly. """ pass class SourceError(AdapterError): """ Exception for problems with the source itself. .. attention:: If you are creating a :term:`source adapter`, this is the only exception you should raise. """ pass class ExistingSectionError(AdapterError): """Exception raised when trying to add an existing group.""" pass class NonExistingSectionError(AdapterError): """Exception raised when trying to use a non-existing group.""" pass class ItemPresentError(AdapterError): """ Exception raised when trying to add an item to a group that already contains it. """ pass class ItemNotPresentError(AdapterError): """ Exception raised when trying to remove an item from a group that doesn't contain it. """ pass #} repoze.what-1.0.9/repoze/what/plugins/0000755000175000017500000000000011356575761017701 5ustar gustavogustavorepoze.what-1.0.9/repoze/what/plugins/__init__.py0000644000175000017500000000164511356575761022020 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2008-2009, Gustavo Narea # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages try: __import__('pkg_resources').declare_namespace(__name__) except ImportError: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) repoze.what-1.0.9/repoze/what/predicates.py0000644000175000017500000005102311356575761020716 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Built-in predicate checkers. This is module provides the predicate checkers that were present in the original "identity" framework of TurboGears 1, plus others. """ from paste.request import parse_formvars, parse_dict_querystring __all__ = ['Predicate', 'CompoundPredicate', 'All', 'Any', 'has_all_permissions', 'has_any_permission', 'has_permission', 'in_all_groups', 'in_any_group', 'in_group', 'is_user', 'is_anonymous', 'not_anonymous', 'PredicateError', 'NotAuthorizedError'] #{ Predicates class Predicate(object): """ Generic predicate checker. This is the base predicate class. It won't do anything useful for you, unless you subclass it. """ def __init__(self, msg=None): """ Create a predicate and use ``msg`` as the error message if it fails. :param msg: The error message, if you want to override the default one defined by the predicate. :type msg: str You may use the ``msg`` keyword argument with any predicate. """ if msg: self.message = msg def evaluate(self, environ, credentials): """ Raise an exception if the predicate is not met. :param environ: The WSGI environment. :type environ: dict :param credentials: The :mod:`repoze.what` ``credentials`` dictionary as a short-cut. :type credentials: dict :raise NotImplementedError: When the predicate doesn't define this method. :raises NotAuthorizedError: If the predicate is not met (use :meth:`unmet` to raise it). This is the method that must be overridden by any predicate checker. For example, if your predicate is "The current month is the specified one", you may define the following predicate checker:: from datetime import date from repoze.what.predicates import Predicate class is_month(Predicate): message = 'The current month must be %(right_month)s' def __init__(self, right_month, **kwargs): self.right_month = right_month super(is_month, self).__init__(**kwargs) def evaluate(self, environ, credentials): today = date.today() if today.month != self.right_month: # Raise an exception because the predicate is not met. self.unmet() .. versionadded:: 1.0.2 .. attention:: Do not evaluate predicates by yourself using this method. See :meth:`check_authorization` and :meth:`is_met`. .. warning:: To make your predicates thread-safe, keep in mind that they may be instantiated at module-level and then shared among many threads, so avoid predicates from being modified after their evaluation. This is, the ``evaluate()`` method should not add, modify or delete any attribute of the predicate. """ self.eval_with_environ(environ) def _eval_with_environ(self, environ): """ Check whether the predicate is met. :param environ: The WSGI environment. :type environ: dict :return: Whether the predicate is met or not. :rtype: bool :raise NotImplementedError: This must be defined by the predicate itself. .. deprecated:: 1.0.2 Only :meth:`evaluate` will be used as of :mod:`repoze.what` v2. """ raise NotImplementedError def eval_with_environ(self, environ): """ Make sure this predicate is met. :param environ: The WSGI environment. :raises NotAuthorizedError: If the predicate is not met. .. versionchanged:: 1.0.1 In :mod:`repoze.what`<1.0.1, this method returned a ``bool`` and set the ``error`` instance attribute of the predicate to the predicate message. .. deprecated:: 1.0.2 Define :meth:`evaluate` instead. """ from warnings import warn msg = 'Predicate._eval_with_environ(environ) is deprecated ' \ 'for forward compatibility with repoze.what v2; define ' \ 'Predicate.evaluate(environ, credentials) instead' warn(msg, DeprecationWarning, stacklevel=2) if not self._eval_with_environ(environ): self.unmet() def unmet(self, msg=None, **placeholders): """ Raise an exception because this predicate is not met. :param msg: The error message to be used; overrides the predicate's default one. :type msg: str :raises NotAuthorizedError: If the predicate is not met. ``placeholders`` represent the placeholders for the predicate message. The predicate's attributes will also be taken into account while creating the message with its placeholders. For example, if you have a predicate that checks that the current month is the specified one, where the predicate message is defined with two placeholders as in:: The current month must be %(right_month)s and it is %(this_month)s and the predicate has an attribute called ``right_month`` which represents the expected month, then you can use this method as in:: self.unmet(this_month=this_month) Then :meth:`unmet` will build the message using the ``this_month`` keyword argument and the ``right_month`` attribute as the placeholders for ``this_month`` and ``right_month``, respectively. So, if ``this_month`` equals ``3`` and ``right_month`` equals ``5``, the message for the exception to be raised will be:: The current month must be 5 and it is 3 If you have a context-sensitive predicate checker and thus you want to change the error message on evaluation, you can call :meth:`unmet` as:: self.unmet('%(this_month)s is not a good month', this_month=this_month) The exception raised would contain the following message:: 3 is not a good month .. versionadded:: 1.0.2 .. versionchanged:: 1.0.4 Introduced the ``msg`` argument. .. attention:: This method should only be called from :meth:`evaluate`. """ if msg: message = msg else: message = self.message # Let's convert it into unicode because it may be just a class, as a # Pylons' "lazy" translation message: message = unicode(message) # Include the predicate attributes in the placeholders: all_placeholders = self.__dict__.copy() all_placeholders.update(placeholders) raise NotAuthorizedError(message % all_placeholders) def check_authorization(self, environ): """ Evaluate the predicate and raise an exception if it's not met. :param environ: The WSGI environment. :raise NotAuthorizedError: If it the predicate is not met. Example:: >>> from repoze.what.predicates import is_user >>> environ = gimme_the_environ() >>> p = is_user('gustavo') >>> p.check_authorization(environ) # ... repoze.what.predicates.NotAuthorizedError: The current user must be "gustavo" .. versionadded:: 1.0.4 Backported from :mod:`repoze.what` v2; deprecates :func:`repoze.what.authorize.check_authorization`. """ logger = environ.get('repoze.who.logger') credentials = environ.get('repoze.what.credentials', {}) try: self.evaluate(environ, credentials) except NotAuthorizedError, error: logger and logger.info(u'Authorization denied: %s' % error) raise logger and logger.info('Authorization granted') def is_met(self, environ): """ Find whether the predicate is met or not. :param environ: The WSGI environment. :return: Whether the predicate is met or not. :rtype: bool Example:: >>> from repoze.what.predicates import is_user >>> environ = gimme_the_environ() >>> p = is_user('gustavo') >>> p.is_met(environ) False .. versionadded:: 1.0.4 Backported from :mod:`repoze.what` v2. """ credentials = environ.get('repoze.what.credentials', {}) try: self.evaluate(environ, credentials) return True except NotAuthorizedError, error: return False def parse_variables(self, environ): """ Return the GET and POST variables in the request, as well as ``wsgiorg.routing_args`` arguments. :param environ: The WSGI environ. :return: The GET and POST variables and ``wsgiorg.routing_args`` arguments. :rtype: dict This is a handy method for request-sensitive predicate checkers. It will return a dictionary for the POST and GET variables, as well as the `wsgiorg.routing_args `_'s ``positional_args`` and ``named_args`` arguments, in the ``post``, ``get``, ``positional_args`` and ``named_args`` items (respectively) of the returned dictionary. For example, if the user submits a form using the POST method to ``http://example.com/blog/hello-world/edit_post?wysiwyg_editor=Yes``, this method may return:: { 'post': {'new_post_contents': 'These are the new contents'}, 'get': {'wysiwyg_editor': 'Yes'}, 'named_args': {'post_slug': 'hello-world'}, 'positional_args': (), } But note that the ``named_args`` and ``positional_args`` items depend completely on how you configured the dispatcher. .. versionadded:: 1.0.4 """ get_vars = parse_dict_querystring(environ) or {} try: post_vars = parse_formvars(environ, False) or {} except KeyError: post_vars = {} routing_args = environ.get('wsgiorg.routing_args', ([], {})) positional_args = routing_args[0] or () named_args = routing_args[1] or {} variables = { 'post': post_vars, 'get': get_vars, 'positional_args': positional_args, 'named_args': named_args} return variables class CompoundPredicate(Predicate): """A predicate composed of other predicates.""" def __init__(self, *predicates, **kwargs): super(CompoundPredicate, self).__init__(**kwargs) self.predicates = predicates class Not(Predicate): """ Negate the specified predicate. :param predicate: The predicate to be negated. Example:: # The user *must* be anonymous: p = Not(not_anonymous()) """ message = u"The condition must not be met" def __init__(self, predicate, **kwargs): super(Not, self).__init__(**kwargs) self.predicate = predicate def evaluate(self, environ, credentials): try: self.predicate.evaluate(environ, credentials) except NotAuthorizedError, error: return self.unmet() class All(CompoundPredicate): """ Check that all of the specified predicates are met. :param predicates: All of the predicates that must be met. Example:: # Grant access if the current month is July and the user belongs to # the human resources group. p = All(is_month(7), in_group('hr')) """ def evaluate(self, environ, credentials): """ Evaluate all the predicates it contains. :param environ: The WSGI environment. :param credentials: The :mod:`repoze.what` ``credentials``. :raises NotAuthorizedError: If one of the predicates is not met. """ for p in self.predicates: p.evaluate(environ, credentials) class Any(CompoundPredicate): """ Check that at least one of the specified predicates is met. :param predicates: Any of the predicates that must be met. Example:: # Grant access if the currest user is Richard Stallman or Linus # Torvalds. p = Any(is_user('rms'), is_user('linus')) """ message = u"At least one of the following predicates must be met: " \ "%(failed_predicates)s" def evaluate(self, environ, credentials): """ Evaluate all the predicates it contains. :param environ: The WSGI environment. :param credentials: The :mod:`repoze.what` ``credentials``. :raises NotAuthorizedError: If none of the predicates is met. """ errors = [] for p in self.predicates: try: p.evaluate(environ, credentials) return except NotAuthorizedError, exc: errors.append(unicode(exc)) failed_predicates = ', '.join(errors) self.unmet(failed_predicates=failed_predicates) class is_user(Predicate): """ Check that the authenticated user's username is the specified one. :param user_name: The required user name. :type user_name: str Example:: p = is_user('linus') """ message = u'The current user must be "%(user_name)s"' def __init__(self, user_name, **kwargs): super(is_user, self).__init__(**kwargs) self.user_name = user_name def evaluate(self, environ, credentials): if credentials and \ self.user_name == credentials.get('repoze.what.userid'): return self.unmet() class in_group(Predicate): """ Check that the user belongs to the specified group. :param group_name: The name of the group to which the user must belong. :type group_name: str Example:: p = in_group('customers') """ message = u'The current user must belong to the group "%(group_name)s"' def __init__(self, group_name, **kwargs): super(in_group, self).__init__(**kwargs) self.group_name = group_name def evaluate(self, environ, credentials): if credentials and self.group_name in credentials.get('groups'): return self.unmet() class in_all_groups(All): """ Check that the user belongs to all of the specified groups. :param groups: The name of all the groups the user must belong to. Example:: p = in_all_groups('developers', 'designers') """ def __init__(self, *groups, **kwargs): group_predicates = [in_group(g) for g in groups] super(in_all_groups,self).__init__(*group_predicates, **kwargs) class in_any_group(Any): """ Check that the user belongs to at least one of the specified groups. :param groups: The name of any of the groups the user may belong to. Example:: p = in_any_group('directors', 'hr') """ message = u"The member must belong to at least one of the following " \ "groups: %(group_list)s" def __init__(self, *groups, **kwargs): self.group_list = ", ".join(groups) group_predicates = [in_group(g) for g in groups] super(in_any_group,self).__init__(*group_predicates, **kwargs) class is_anonymous(Predicate): """ Check that the current user is anonymous. Example:: # The user must be anonymous! p = is_anonymous() .. versionadded:: 1.0.7 """ message = u"The current user must be anonymous" def evaluate(self, environ, credentials): if credentials: self.unmet() class not_anonymous(Predicate): """ Check that the current user has been authenticated. Example:: # The user must have been authenticated! p = not_anonymous() """ message = u"The current user must have been authenticated" def evaluate(self, environ, credentials): if not credentials: self.unmet() class has_permission(Predicate): """ Check that the current user has the specified permission. :param permission_name: The name of the permission that must be granted to the user. Example:: p = has_permission('hire') """ message = u'The user must have the "%(permission_name)s" permission' def __init__(self, permission_name, **kwargs): super(has_permission, self).__init__(**kwargs) self.permission_name = permission_name def evaluate(self, environ, credentials): if credentials and \ self.permission_name in credentials.get('permissions'): return self.unmet() class has_all_permissions(All): """ Check that the current user has been granted all of the specified permissions. :param permissions: The names of all the permissions that must be granted to the user. Example:: p = has_all_permissions('view-users', 'edit-users') """ def __init__(self, *permissions, **kwargs): permission_predicates = [has_permission(p) for p in permissions] super(has_all_permissions, self).__init__(*permission_predicates, **kwargs) class has_any_permission(Any): """ Check that the user has at least one of the specified permissions. :param permissions: The names of any of the permissions that have to be granted to the user. Example:: p = has_any_permission('manage-users', 'edit-users') """ message = u"The user must have at least one of the following " \ "permissions: %(permission_list)s" def __init__(self, *permissions, **kwargs): self.permission_list = ", ".join(permissions) permission_predicates = [has_permission(p) for p in permissions] super(has_any_permission,self).__init__(*permission_predicates, **kwargs) #{ Exceptions class PredicateError(Exception): """ Former exception raised by a :class:`Predicate` if it's not met. .. deprecated:: 1.0.4 Deprecated in favor of :class:`NotAuthorizedError`, for forward compatibility with :mod:`repoze.what` v2. """ # Ugly workaround for Python < 2.6: if not hasattr(Exception, '__unicode__'): def __unicode__(self): return unicode(self.args and self.args[0] or '') class NotAuthorizedError(PredicateError): """ Exception raised by :meth:`Predicate.check_authorization` if the subject is not allowed to access the requested source. This exception deprecates :class:`PredicateError` as of v1.0.4, but extends it to avoid breaking backwards compatibility. .. versionchanged:: 1.0.4 This exception was defined at :mod:`repoze.what.authorize` until version 1.0.3, but is still imported into that module to keep backwards compatibility with v1.X releases -- but it won't work in :mod:`repoze.what` v2. """ pass #} repoze.what-1.0.9/repoze/what/authorize.py0000644000175000017500000000364411356575761020613 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2008-2009, Gustavo Narea # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Utilities to restrict access based on predicates. .. deprecated:: 1.0.4 This module won't be available in :mod:`repoze.what` v2. See :meth:`repoze.what.predicates.Predicate.check_authorization`. """ from warnings import warn # We import the predicates just to make repoze.what backwards compatible # with tg.ext.repoze.who, but they are actually useless here: from repoze.what.predicates import * def check_authorization(predicate, environ): """ Verify if the current user really can access the requested source. :param predicate: The predicate to be evaluated. :param environ: The WSGI environment. :raise NotAuthorizedError: If it the predicate is not met. .. deprecated:: 1.0.4 Use :meth:`repoze.what.predicates.Predicate.check_authorization` instead. .. versionchanged:: 1.0.4 :class:`repoze.what.predicates.PredicateError` used to be the exception raised. """ msg = 'repoze.what.authorize is deprecated for forward compatibility '\ 'with repoze.what v2; use ' \ 'Predicate.check_authorization(environ) instead' warn(msg, DeprecationWarning, stacklevel=2) if predicate is not None: predicate.check_authorization(environ) repoze.what-1.0.9/repoze/what/__init__.py0000644000175000017500000000232611356575761020334 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Authorization framework for WSGI applications. """ # This is a package. Do NOT put anything here to keep the repoze.what # namespace clear for plugins at repoze.what.plugins # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages try: __import__('pkg_resources').declare_namespace(__name__) except ImportError: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) repoze.what-1.0.9/repoze/what/middleware.py0000644000175000017500000002130111356575761020704 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Utilities to setup authorization by configuring repoze.who's middleware to use repoze.what. """ import os from zope.interface import implements from repoze.who.plugins.testutil import make_middleware from repoze.who.classifiers import default_challenge_decider, \ default_request_classifier from repoze.who.interfaces import IAuthenticator, IMetadataProvider __all__ = ['AuthorizationMetadata', 'setup_auth'] class AuthorizationMetadata(object): """ repoze.who metadata provider to load groups and permissions data for the current user. There's no need to include this class in the end-user documentation, as there's no reason why they may ever need it... It's only by :func:`setup_auth`. """ implements(IMetadataProvider) def __init__(self, group_adapters=None, permission_adapters=None): """ Fetch the groups and permissions of the authenticated user. :param group_adapters: Set of adapters that retrieve the known groups of the application, each identified by a keyword. :type group_adapters: dict :param permission_adapters: Set of adapters that retrieve the permissions for the groups, each identified by a keyword. :type permission_adapters: dict """ self.group_adapters = group_adapters self.permission_adapters = permission_adapters def _find_groups(self, identity): """ Return the groups to which the authenticated user belongs, as well as the permissions granted to such groups. """ groups = set() permissions = set() if self.group_adapters is not None: # repoze.what-2.X group adapters expect to find the # 'repoze.what.userid' key in the credentials credentials = identity.copy() credentials['repoze.what.userid'] = identity['repoze.who.userid'] # It's using groups/permissions-based authorization for grp_fetcher in self.group_adapters.values(): groups |= set(grp_fetcher.find_sections(credentials)) for group in groups: for perm_fetcher in self.permission_adapters.values(): permissions |= set(perm_fetcher.find_sections(group)) return tuple(groups), tuple(permissions) # IMetadataProvider def add_metadata(self, environ, identity): """ Load the groups and permissions of the authenticated user. It will load such data into the :mod:`repoze.who` ``identity`` and the :mod:`repoze.what` ``credentials`` dictionaries. :param environ: The WSGI environment. :param identity: The :mod:`repoze.who`'s ``identity`` dictionary. """ logger = environ.get('repoze.who.logger') # Finding the groups and permissions: groups, permissions = self._find_groups(identity) identity['groups'] = groups identity['permissions'] = permissions # Adding the groups and permissions to the repoze.what credentials for # forward compatibility: if 'repoze.what.credentials' not in environ: environ['repoze.what.credentials'] = {} environ['repoze.what.credentials']['groups'] = groups environ['repoze.what.credentials']['permissions'] = permissions # Adding the userid: userid = identity['repoze.who.userid'] environ['repoze.what.credentials']['repoze.what.userid'] = userid # Adding the adapters: environ['repoze.what.adapters'] = { 'groups': self.group_adapters, 'permissions': self.permission_adapters } # Logging logger and logger.info('User belongs to the following groups: %s' % str(groups)) logger and logger.info('User has the following permissions: %s' % str(permissions)) def setup_auth(app, group_adapters=None, permission_adapters=None, **who_args): """ Setup :mod:`repoze.who` with :mod:`repoze.what` support. :param app: The WSGI application object. :param group_adapters: The group source adapters to be used. :type group_adapters: dict :param permission_adapters: The permission source adapters to be used. :type permission_adapters: dict :param who_args: Authentication-related keyword arguments to be passed to :mod:`repoze.who`. :return: The WSGI application with authentication and authorization middleware. .. tip:: If you are looking for an easier way to get started, you may want to use :mod:`the quickstart plugin ` and its :func:`setup_sql_auth() ` function. You must define the ``group_adapters`` and ``permission_adapters`` keyword arguments if you want to use the groups/permissions-based authorization pattern. Additional keyword arguments will be passed to :func:`repoze.who.plugins.testutil.make_middleware` -- and among those keyword arguments, you *must* define at least the identifier(s), authenticator(s) and challenger(s) to be used. For example:: from repoze.who.plugins.basicauth import BasicAuthPlugin from repoze.who.plugins.htpasswd import HTPasswdPlugin, crypt_check from repoze.what.middleware import setup_auth from repoze.what.plugins.xml import XMLGroupsAdapter from repoze.what.plugins.ini import INIPermissionAdapter # Defining the group adapters; you may add as much as you need: groups = {'all_groups': XMLGroupsAdapter('/path/to/groups.xml')} # Defining the permission adapters; you may add as much as you need: permissions = {'all_perms': INIPermissionAdapter('/path/to/perms.ini')} # repoze.who identifiers; you may add as much as you need: basicauth = BasicAuthPlugin('Private web site') identifiers = [('basicauth', basicauth)] # repoze.who authenticators; you may add as much as you need: htpasswd_auth = HTPasswdPlugin('/path/to/users.htpasswd', crypt_check) authenticators = [('htpasswd', htpasswd_auth)] # repoze.who challengers; you may add as much as you need: challengers = [('basicauth', basicauth)] app_with_auth = setup_auth( app, groups, permissions, identifiers=identifiers, authenticators=authenticators, challengers=challengers) .. attention:: Keep in mind that :mod:`repoze.who` must be configured `through` :mod:`repoze.what` for authorization to work. .. note:: If you want to skip authentication while testing your application, you should pass the ``skip_authentication`` keyword argument with a value that evaluates to ``True``. .. versionchanged:: 1.0.5 :class:`repoze.who.middleware.PluggableAuthenticationMiddleware` replaced with :func:`repoze.who.plugins.testutil.make_middleware` internally. """ authorization = AuthorizationMetadata(group_adapters, permission_adapters) if 'mdproviders' not in who_args: who_args['mdproviders'] = [] who_args['mdproviders'].append(('authorization_md', authorization)) if 'classifier' not in who_args: who_args['classifier'] = default_request_classifier if 'challenge_decider' not in who_args: who_args['challenge_decider'] = default_challenge_decider auth_log = os.environ.get('AUTH_LOG', '') == '1' if auth_log: import sys who_args['log_stream'] = sys.stdout skip_authn = who_args.pop('skip_authentication', False) middleware = make_middleware(skip_authn, app, **who_args) return middleware repoze.what-1.0.9/setup.py0000644000175000017500000000522111356575761015463 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007, Agendaless Consulting and Contributors. # Copyright (c) 2008, Florent Aide . # Copyright (c) 2008-2009, Gustavo Narea . # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## import os from ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) README = open(os.path.join(here, 'README.txt')).read() version = open(os.path.join(here, 'VERSION.txt')).readline().rstrip() setup(name='repoze.what', version=version, description=('Authorization framework for WSGI applications'), long_description=README, classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Security", "Topic :: Software Development :: Libraries :: Application Frameworks" ], keywords='authorization web application server wsgi repoze', author="Gustavo Narea", author_email="repoze-dev@lists.repoze.org", namespace_packages = ['repoze', 'repoze.what', 'repoze.what.plugins'], url="http://static.repoze.org/whatdocs/", license="BSD-derived (http://www.repoze.org/LICENSE.txt)", packages=find_packages(exclude=['tests', 'functional_tests']), package_data={ '': ['VERSION.txt', 'README.txt'], 'docs': ['Makefile', 'source/*']}, exclude_package_data={'': ['README.txt', 'docs']}, include_package_data=True, zip_safe=False, tests_require = [ 'repoze.who >= 1.0', 'repoze.who-testutil >= 1.0b2', 'Paste', 'coverage', 'nose', ], install_requires=[ 'repoze.who >= 1.0, <= 1.99', 'repoze.who-testutil >= 1.0b2', 'Paste > 1.7', ], test_suite="nose.collector", entry_points = """\ """ ) repoze.what-1.0.9/functional_tests/0000755000175000017500000000000011356575761017335 5ustar gustavogustavorepoze.what-1.0.9/functional_tests/__init__.py0000644000175000017500000000134411356575761021450 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2008-2009, Gustavo Narea # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Functional tests for :mod:`repoze.what`. """ repoze.what-1.0.9/functional_tests/test_threading.py0000644000175000017500000002375511356575761022727 0ustar gustavogustavo# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2009, Gustavo Narea # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## """ Ensure :mod:`repoze.what` is thread-safe when some components are instantiated at module-level and shared among threads. Thanks to Alberto Valverde for helping me write these tests! """ import sys, time from threading import Thread, Event, Timer from string import ascii_letters from unittest import TestCase from repoze.what.predicates import * from repoze.what.authorize import check_authorization, NotAuthorizedError #{ The test cases class TestPredicateErrors(TestCase): """ Test that all the built-in predicates are thread-safe. Alberto Valverde found that "All"-based predicates were not thread-safe because if they are instantiated at module-level and used among many threads, their error message of a predicate that failed on one thread would override the message of a failed predicate on another thread -- that's because there'd be only one predicate, not one per thread. So this test case would ensure this won't happen again, on any other predicate, "All"-based or not. """ def setUp(self): self.found_error = Event() self.threads = [] self.threads_stopped = False def tearDown(self): self._stop_threads() def _share_predicate_among_threads(self, shared_predicate, scenarios): """ Share predicate ``shared_predicate`` among many threads (one per scenario). """ # First define the scenarios to be run in one thread for scenario in scenarios: thread = DaPredicateThread(scenario['credentials'], shared_predicate, scenario['error'], self.found_error) self.threads.append(thread) # Configuring the timer that is going to stop threads when the # predicate in question IS thread-safe. t = Timer(2, self._stop_threads) t.start() # Running the threads: map(Thread.start, self.threads) if self.found_error.isSet(): predicate_class = shared_predicate.__class__.__name__ self.fail('Predicate %s is not thread-safe' % predicate_class) def _stop_threads(self): """Stop all the threads""" if self.threads_stopped: return for thread in self.threads: thread.stop = True self.threads_stopped = True def test_is_user(self): """The is_user predicate is thread-safe.""" # Building the shared predicate: users = set(ascii_letters) shared_predicate = is_user('foo') error = 'The current user must be "foo"' # Building the test scenarios that will share the predicate above: scenarios = [] for u in users: credentials = {'repoze.what.userid': u} scenario = {'credentials': credentials, 'error': error} scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) def test_in_group(self): """The in_group predicate is thread-safe.""" # Building the shared predicate: groups = set(ascii_letters) shared_predicate = in_group('foo') error = 'The current user must belong to the group "foo"' # Building the test scenarios that will share the predicate above: scenarios = [] for g in groups: credentials = {'groups': groups.copy()} scenario = {'credentials': credentials, 'error': error} scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) def test_in_all_groups(self): """The in_all_groups predicate is thread-safe.""" # Building the shared predicate: all_groups = set(ascii_letters) shared_predicate = in_all_groups(*all_groups) # Building the test scenarios that will share the predicate above: scenarios = [] for g in all_groups: error = 'The current user must belong to the group "%s"' % g scenario = { 'credentials': {'groups': all_groups - set([g])}, 'error': error, } scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) def test_in_any_group(self): """The in_any_group predicate is thread-safe.""" # Building the shared predicate: all_groups = set(ascii_letters) shared_predicate = in_any_group(*all_groups) error = "The member must belong to at least one of the following "\ "groups: " error = error + ', '.join(all_groups) # Building the test scenarios that will share the predicate above: credentials = {'groups': set([u"ñ", u"é"])} scenarios = [] for g in all_groups: scenario = {'credentials': credentials, 'error': error} scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) def test_has_permission(self): """The has_permission predicate is thread-safe.""" # Building the shared predicate: perms = set(ascii_letters) shared_predicate = has_permission('foo') error = 'The user must have the "foo" permission' # Building the test scenarios that will share the predicate above: scenarios = [] for p in perms: credentials = {'permissions': perms.copy()} scenario = {'credentials': credentials, 'error': error} scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) def test_has_all_permissions(self): """The has_all_permissions predicate is thread-safe.""" # Building the shared predicate: all_perms = set(ascii_letters) shared_predicate = has_all_permissions(*all_perms) # Building the test scenarios that will share the predicate above: scenarios = [] for p in all_perms: error = 'The user must have the "%s" permission' % p scenario = { 'credentials': {'permissions': all_perms - set([p])}, 'error': error, } scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) def test_has_any_permission(self): """The has_any_permission predicate is thread-safe.""" # Building the shared predicate: all_perms = set(ascii_letters) shared_predicate = has_any_permission(*all_perms) error = "The user must have at least one of the following "\ "permissions: " error = error + ', '.join(all_perms) # Building the test scenarios that will share the predicate above: credentials = {'permissions': set([u"ñ", u"é"])} scenarios = [] for p in all_perms: scenario = {'credentials': credentials, 'error': error} scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) def test_All(self): """The All predicate is thread-safe.""" # Building the shared predicate: all_groups = set(ascii_letters) shared_predicate = All(*map(in_group, all_groups)) # Building the test scenarios that will share the predicate above: scenarios = [] for g in all_groups: error = 'The current user must belong to the group "%s"' % g scenario = { 'credentials': {'groups': all_groups - set([g])}, 'error': error, } scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) def test_Any(self): """The Any predicate is thread-safe.""" # Building the shared predicate: expected_users = set(ascii_letters) shared_predicate = Any(*map(is_user, expected_users)) error = "At least one of the following predicates must be met:" for u in expected_users: error = error + ' The current user must be "%s",' % u error = error[:-1] # Building the test scenarios that will share the predicate above: credentials = {'repoze.what.userid': None} scenarios = [] for u in expected_users: scenario = {'credentials': credentials, 'error': error} scenarios.append(scenario) self._share_predicate_among_threads(shared_predicate, scenarios) #{ Test utilities class DaPredicateThread(Thread): def __init__(self, credentials, shared_predicate, expected_error, found_error, *args, **kwargs): super(DaPredicateThread, self).__init__(*args, **kwargs) self.credentials = credentials self.shared_predicate = shared_predicate self.expected_error = expected_error self.found_error = found_error self.stop = False def run(self): while not self.found_error.isSet() and not self.stop: # Create a new environ simulating the fresh environ each request gets environ = {'repoze.what.credentials': self.credentials.copy()} try: check_authorization(self.shared_predicate, environ) except NotAuthorizedError, exc: if unicode(exc) != self.expected_error: self.found_error.set() #} repoze.what-1.0.9/ez_setup.py0000644000175000017500000002231311356575761016162 0ustar gustavogustavo#!python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6c8" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', } import sys, os def _validate_md5(egg_name, data): if egg_name in md5_data: from md5 import md5 digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print >>sys.stderr, ( "md5 validation of %s failed! (Possible download problem?)" % egg_name ) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules def do_download(): egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg try: import pkg_resources except ImportError: return do_download() try: pkg_resources.require("setuptools>="+version); return except pkg_resources.VersionConflict, e: if was_imported: print >>sys.stderr, ( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first, using 'easy_install -U setuptools'." "\n\n(Currently using %r)" ) % (version, e.args[0]) sys.exit(2) else: del pkg_resources, sys.modules['pkg_resources'] # reload ok return do_download() except pkg_resources.DistributionNotFound: return do_download() def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib2, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib2.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: egg = None try: egg = download_setuptools(version, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: if egg and os.path.exists(egg): os.unlink(egg) else: if setuptools.__version__ == '0.0.1': print >>sys.stderr, ( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ) sys.exit(2) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print "Setuptools version",version,"or greater has been installed." print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' def update_md5(filenames): """Update our built-in md5 registry""" import re from md5 import md5 for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in md5_data.items()] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print >>sys.stderr, "Internal error!" sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:])