bottle-cork-0.12.0/0000755000175000017500000000000012617467123013660 5ustar fedefede00000000000000bottle-cork-0.12.0/PKG-INFO0000644000175000017500000000224212617467123014755 0ustar fedefede00000000000000Metadata-Version: 1.1 Name: bottle-cork Version: 0.12.0 Summary: Authentication/Authorization library for Bottle Home-page: http://cork.firelet.net/ Author: Federico Ceratto Author-email: federico.ceratto@gmail.com License: LGPLv3+ Description: Cork is a simple Authentication/Authorization libraryfor the Bottle and Flask web frameworks. Platform: Linux Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Framework :: Bottle Classifier: Framework :: Flask Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Natural Language :: English Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Security Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Python Modules bottle-cork-0.12.0/README.rst0000644000175000017500000000174012512453671015346 0ustar fedefede00000000000000Cork - Authentication for the Bottle web framework .. image:: https://secure.travis-ci.org/FedericoCeratto/bottle-cork.png?branch=master :target: http://travis-ci.org/FedericoCeratto/bottle-cork :alt: Build status .. image:: https://coveralls.io/repos/FedericoCeratto/bottle-cork/badge.png?branch=master :target: https://coveralls.io/r/FedericoCeratto/bottle-cork?branch=master :alt: Coverage .. image:: https://pypip.in/download/bottle-cork/badge.png :target: https://pypi.python.org/pypi//bottle-cork/ :alt: Downloads .. image:: https://pypip.in/version/bottle-cork/badge.png :target: https://pypi.python.org/pypi/bottle-cork/ :alt: Latest Version .. image:: https://pypip.in/license/bottle-cork/badge.png :target: https://pypi.python.org/pypi/bottle-cork/ :alt: License Cork provides a simple set of methods to implement Authentication and Authorization in web applications based on Bottle. Website and Documentation: http://cork.firelet.net bottle-cork-0.12.0/setup.cfg0000644000175000017500000000007312617467123015501 0ustar fedefede00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 bottle-cork-0.12.0/tests/0000755000175000017500000000000012617467123015022 5ustar fedefede00000000000000bottle-cork-0.12.0/tests/simple_webapp_decorated.py0000777000175000017500000000000012512453671031473 2../examples/simple_webapp_decorated.pyustar fedefede00000000000000bottle-cork-0.12.0/tests/test_flask.py0000644000175000017500000010055512512453671017536 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Unit testing # from base64 import b64encode, b64decode from nose import SkipTest from nose.tools import assert_raises, raises, with_setup from time import time import mock import os import shutil from cork import Cork, JsonBackend, AAAException, AuthException from cork import Mailer from cork.base_backend import BackendIOException import testutils testdir = None # Test directory aaa = None # global Cork instance cookie_name = None # global variable to track cookie status tmproot = testutils.pick_temp_directory() class RoAttrDict(dict): """Read-only attribute-accessed dictionary. Used to mock beaker's session objects """ def __getattr__(self, name): return self[name] class MockedAdminCork(Cork): """Mocked module where the current user is always 'admin'""" @property def _beaker_session(self): return RoAttrDict(username='admin') def _setup_cookie(self, username): global cookie_name cookie_name = username class MockedUnauthenticatedCork(Cork): """Mocked module where the current user not set""" @property def _beaker_session(self): return RoAttrDict() def _setup_cookie(self, username): global cookie_name cookie_name = username def setup_empty_dir(): """Setup test directory without JSON files""" global testdir tstamp = "%f" % time() testdir = "%s/fl_%s" % (tmproot, tstamp) os.mkdir(testdir) os.mkdir(testdir + '/view') print("setup done in %s" % testdir) def setup_dir(): """Setup test directory with valid JSON files""" global testdir tstamp = "%f" % time() testdir = "%s/fl_%s" % (tmproot, tstamp) os.mkdir(testdir) os.mkdir(testdir + '/views') with open("%s/users.json" % testdir, 'w') as f: f.write("""{"admin": {"email_addr": null, "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596"}}""") with open("%s/roles.json" % testdir, 'w') as f: f.write("""{"special": 200, "admin": 100, "user": 50}""") with open("%s/register.json" % testdir, 'w') as f: f.write("""{}""") with open("%s/views/registration_email.tpl" % testdir, 'w') as f: f.write("""Username:{{username}} Email:{{email_addr}} Code:{{registration_code}}""") with open("%s/views/password_reset_email.tpl" % testdir, 'w') as f: f.write("""Username:{{username}} Email:{{email_addr}} Code:{{reset_code}}""") print("setup done in %s" % testdir) def setup_mockedadmin(): """Setup test directory and a MockedAdminCork instance""" global aaa global cookie_name setup_dir() aaa = MockedAdminCork(testdir, smtp_server='localhost', email_sender='test@localhost') cookie_name = None def setup_mocked_unauthenticated(): """Setup test directory and a MockedAdminCork instance""" global aaa global cookie_name setup_dir() aaa = MockedUnauthenticatedCork(testdir) cookie_name = None def teardown_dir(): global cookie_name global testdir if testdir: shutil.rmtree(testdir) testdir = None cookie_name = None @with_setup(setup_dir, teardown_dir) def test_init(): Cork(testdir) @with_setup(setup_dir, teardown_dir) def test_initialize_storage(): jb = JsonBackend(testdir, initialize=True) Cork(backend=jb) with open("%s/users.json" % testdir) as f: assert f.readlines() == ['{}'] with open("%s/roles.json" % testdir) as f: assert f.readlines() == ['{}'] with open("%s/register.json" % testdir) as f: assert f.readlines() == ['{}'] with open("%s/views/registration_email.tpl" % testdir) as f: assert f.readlines() == [ 'Username:{{username}} Email:{{email_addr}} Code:{{registration_code}}'] with open("%s/views/password_reset_email.tpl" % testdir) as f: assert f.readlines() == [ 'Username:{{username}} Email:{{email_addr}} Code:{{reset_code}}'] @raises(BackendIOException) @with_setup(setup_dir, teardown_dir) def test_unable_to_save(): bogus_dir = '/___inexisting_directory___' Cork(bogus_dir, initialize=True) @with_setup(setup_mockedadmin, teardown_dir) def test_mockedadmin(): assert len(aaa._store.users) == 1, repr(aaa._store.users) assert 'admin' in aaa._store.users, repr(aaa._store.users) @raises(BackendIOException) @with_setup(setup_mockedadmin, teardown_dir) def test_loadjson_missing_file(): aaa._store._loadjson('nonexistent_file', {}) @raises(BackendIOException) @with_setup(setup_mockedadmin, teardown_dir) def test_loadjson_broken_file(): with open(testdir + '/broken_file.json', 'w') as f: f.write('-----') aaa._store._loadjson('broken_file', {}) @with_setup(setup_mockedadmin, teardown_dir) def test_loadjson_unchanged(): # By running _refresh with unchanged files the files should not be reloaded mtimes = aaa._store._mtimes aaa._store._refresh() # The test simply ensures that no mtimes have been updated assert mtimes == aaa._store._mtimes # Test PBKDF2-based password hashing @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_PBKDF2(): shash = aaa._hash('user_foo', 'bogus_pwd') assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith('='), "hash should end with '='" assert aaa._verify_password('user_foo', 'bogus_pwd', shash) == True, \ "Hashing verification should succeed" @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_PBKDF2_known_hash(): salt = 's' * 32 shash = aaa._hash('user_foo', 'bogus_pwd', salt=salt) assert shash == 'cHNzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzax44AxQgK6uD9q1YWxLos1ispCe1Z7T7pOFK1PwdWEs=' @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_PBKDF2_known_hash_2(): salt = '\0' * 32 shash = aaa._hash('user_foo', 'bogus_pwd', salt=salt) assert shash == 'cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8Uh4pyEOHoRz4j0lDzAmqb7Dvmo8GpeXwiKTDsuYFw=' @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_PBKDF2_known_hash_3(): salt = 'x' * 32 shash = aaa._hash('user_foo', 'bogus_pwd', salt=salt) assert shash == 'cHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4MEaIU5Op97lmvwX5NpVSTBP8jg8OlrN7c2K8K8tnNks=' @raises(AssertionError) @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_PBKDF2_incorrect_hash_len(): salt = 'x' * 31 # Incorrect length shash = aaa._hash('user_foo', 'bogus_pwd', salt=salt) @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_PBKDF2_incorrect_hash_value(): shash = aaa._hash('user_foo', 'bogus_pwd') assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith('='), "hash should end with '='" assert aaa._verify_password('user_foo', '####', shash) == False, \ "Hashing verification should fail" assert aaa._verify_password('###', 'bogus_pwd', shash) == False, \ "Hashing verification should fail" @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_PBKDF2_collision(): salt = 'S' * 32 hash1 = aaa._hash('user_foo', 'bogus_pwd', salt=salt) hash2 = aaa._hash('user_foobogus', '_pwd', salt=salt) assert hash1 != hash2, "Hash collision" # Test password hashing for inexistent algorithms @raises(RuntimeError) @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_bogus_algo(): aaa._hash('user_foo', 'bogus_pwd', algo='bogus_algo') @raises(RuntimeError) @with_setup(setup_mockedadmin, teardown_dir) def test_password_hashing_bogus_algo_during_verify(): # Incorrect hash type (starts with "X") shash = b64encode('X' + 'bogusstring') aaa._verify_password('user_foo', 'bogus_pwd', shash) # End of password hashing tests @with_setup(setup_mockedadmin, teardown_dir) def test_unauth_create_role(): aaa._store.roles['admin'] = 10 # lower admin level assert_raises(AuthException, aaa.create_role, 'user', 33) @with_setup(setup_mockedadmin, teardown_dir) def test_create_existing_role(): assert_raises(AAAException, aaa.create_role, 'user', 33) @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) def test_create_role_with_incorrect_level(): aaa.create_role('new_user', 'not_a_number') @with_setup(setup_mockedadmin, teardown_dir) def test_create_role(): assert len(aaa._store.roles) == 3, repr(aaa._store.roles) aaa.create_role('user33', 33) assert len(aaa._store.roles) == 4, repr(aaa._store.roles) fname = "%s/%s.json" % (aaa._store._directory, aaa._store._roles_fname) with open(fname) as f: data = f.read() assert 'user33' in data, repr(data) @SkipTest @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) def test_create_empty_role(): aaa.create_role('', 42) @with_setup(setup_mockedadmin, teardown_dir) def test_unauth_delete_role(): aaa._store.roles['admin'] = 10 # lower admin level assert_raises(AuthException, aaa.delete_role, 'user') @with_setup(setup_mockedadmin, teardown_dir) def test_delete_nonexistent_role(): assert_raises(AAAException, aaa.delete_role, 'user123') @with_setup(setup_mockedadmin, teardown_dir) def test_create_delete_role(): assert len(aaa._store.roles) == 3, repr(aaa._store.roles) aaa.create_role('user33', 33) assert len(aaa._store.roles) == 4, repr(aaa._store.roles) fname = "%s/%s.json" % (aaa._store._directory, aaa._store._roles_fname) with open(fname) as f: data = f.read() assert 'user33' in data, repr(data) assert aaa._store.roles['user33'] == 33 aaa.delete_role('user33') assert len(aaa._store.roles) == 3, repr(aaa._store.roles) @with_setup(setup_mockedadmin, teardown_dir) def test_list_roles(): roles = list(aaa.list_roles()) assert len(roles) == 3, "Incorrect. Users are: %s" % repr(aaa._store.roles) @with_setup(setup_mockedadmin, teardown_dir) def test_unauth_create_user(): aaa._store.roles['admin'] = 10 # lower admin level assert_raises(AuthException, aaa.create_user, 'phil', 'user', 'hunter123') @with_setup(setup_mockedadmin, teardown_dir) def test_create_existing_user(): assert_raises(AAAException, aaa.create_user, 'admin', 'admin', 'bogus') @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) def test_create_user_with_wrong_role(): aaa.create_user('admin2', 'nonexistent_role', 'bogus') @with_setup(setup_mockedadmin, teardown_dir) def test_create_user(): assert len(aaa._store.users) == 1, repr(aaa._store.users) aaa.create_user('phil', 'user', 'user') assert len(aaa._store.users) == 2, repr(aaa._store.users) fname = "%s/%s.json" % (aaa._store._directory, aaa._store._users_fname) with open(fname) as f: data = f.read() assert 'phil' in data, repr(data) @with_setup(setup_mockedadmin, teardown_dir) def test_unauth_delete_user(): aaa._store.roles['admin'] = 10 # lower admin level assert_raises(AuthException, aaa.delete_user, 'phil') @with_setup(setup_mockedadmin, teardown_dir) def test_delete_nonexistent_user(): assert_raises(AAAException, aaa.delete_user, 'not_an_user') @with_setup(setup_mockedadmin, teardown_dir) def test_delete_user(): assert len(aaa._store.users) == 1, repr(aaa._store.users) aaa.delete_user('admin') assert len(aaa._store.users) == 0, repr(aaa._store.users) fname = "%s/%s.json" % (aaa._store._directory, aaa._store._users_fname) with open(fname) as f: data = f.read() assert 'admin' not in data, "'admin' must not be in %s" % repr(data) @with_setup(setup_mockedadmin, teardown_dir) def test_list_users(): users = list(aaa.list_users()) assert len(users) == 1, "Incorrect. Users are: %s" % repr(aaa._store.users) @with_setup(setup_mockedadmin, teardown_dir) def test_failing_login(): login = aaa.login('phil', 'hunter123') assert login == False, "Login must fail" global cookie_name assert cookie_name == None @with_setup(setup_mockedadmin, teardown_dir) def test_login_nonexistent_user_empty_password(): login = aaa.login('IAmNotHome', '') assert login == False, "Login must fail" global cookie_name assert cookie_name == None @with_setup(setup_mockedadmin, teardown_dir) def test_login_existing_user_empty_password(): aaa.create_user('phil', 'user', 'hunter123') assert 'phil' in aaa._store.users assert aaa._store.users['phil']['role'] == 'user' login = aaa.login('phil', '') assert login == False, "Login must fail" global cookie_name assert cookie_name == None @with_setup(setup_mockedadmin, teardown_dir) def test_create_and_validate_user(): aaa.create_user('phil', 'user', 'hunter123') assert 'phil' in aaa._store.users assert aaa._store.users['phil']['role'] == 'user' login = aaa.login('phil', 'hunter123') assert login == True, "Login must succeed" global cookie_name assert cookie_name == 'phil' @with_setup(setup_mockedadmin, teardown_dir) def test_require_failing_username(): # The user exists, but I'm 'admin' aaa.create_user('phil', 'user', 'hunter123') assert_raises(AuthException, aaa.require, username='phil') @with_setup(setup_mockedadmin, teardown_dir) def test_require_nonexistent_username(): assert_raises(AAAException, aaa.require, username='no_such_user') @with_setup(setup_mockedadmin, teardown_dir) def test_require_failing_role_fixed(): assert_raises(AuthException, aaa.require, role='user', fixed_role=True) @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) def test_require_missing_parameter(): aaa.require(fixed_role=True) @with_setup(setup_mockedadmin, teardown_dir) def test_require_nonexistent_role(): assert_raises(AAAException, aaa.require, role='clown') @with_setup(setup_mockedadmin, teardown_dir) def test_require_failing_role(): # Requesting level >= 100 assert_raises(AuthException, aaa.require, role='special') @with_setup(setup_mockedadmin, teardown_dir) def test_successful_require_role(): aaa.require(username='admin') aaa.require(username='admin', role='admin') aaa.require(username='admin', role='admin', fixed_role=True) aaa.require(username='admin', role='user') @with_setup(setup_mockedadmin, teardown_dir) def test_authenticated_is_not__anonymous(): assert not aaa.user_is_anonymous @with_setup(setup_mockedadmin, teardown_dir) def test_update_nonexistent_role(): assert_raises(AAAException, aaa.current_user.update, role='clown') @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) def test_update_nonexistent_user(): aaa._store.users.pop('admin') aaa.current_user.update(role='user') @with_setup(setup_mockedadmin, teardown_dir) def test_update_role(): aaa.current_user.update(role='user') assert aaa._store.users['admin']['role'] == 'user' @with_setup(setup_mockedadmin, teardown_dir) def test_update_pwd(): aaa.current_user.update(pwd='meow') @with_setup(setup_mockedadmin, teardown_dir) def test_update_email(): aaa.current_user.update(email_addr='foo') assert aaa._store.users['admin']['email_addr'] == 'foo' @raises(AAAException) @with_setup(setup_mocked_unauthenticated, teardown_dir) def test_get_current_user_unauth(): aaa.current_user['username'] @with_setup(setup_mocked_unauthenticated, teardown_dir) def test_unauth_is_anonymous(): assert aaa.user_is_anonymous @raises(AuthException) @with_setup(setup_mockedadmin, teardown_dir) def test_get_current_user_nonexistent(): # The current user 'admin' is not in the user table aaa._store.users.pop('admin') aaa.current_user @with_setup(setup_mockedadmin, teardown_dir) def test_get_nonexistent_user(): assert aaa.user('nonexistent_user') is None @with_setup(setup_mockedadmin, teardown_dir) def test_get_user_description_field(): admin = aaa.user('admin') for field in ['description', 'email_addr']: assert field in admin.__dict__ @with_setup(setup_mockedadmin, teardown_dir) def test_register_no_user(): assert_raises(AssertionError, aaa.register, None, 'pwd', 'a@a.a') @with_setup(setup_mockedadmin, teardown_dir) def test_register_no_pwd(): assert_raises(AssertionError, aaa.register, 'foo', None, 'a@a.a') @with_setup(setup_mockedadmin, teardown_dir) def test_register_no_email(): assert_raises(AssertionError, aaa.register, 'foo', 'pwd', None) @with_setup(setup_mockedadmin, teardown_dir) def test_register_already_existing(): assert_raises(AAAException, aaa.register, 'admin', 'pwd', 'a@a.a') @with_setup(setup_mockedadmin, teardown_dir) def test_register_no_role(): assert_raises(AAAException, aaa.register, 'foo', 'pwd', 'a@a.a', role='clown') @with_setup(setup_mockedadmin, teardown_dir) def test_register_role_too_high(): assert_raises(AAAException, aaa.register, 'foo', 'pwd', 'a@a.a', role='admin') # Patch the mailer _send() method to prevent network interactions @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_register(mocked): old_dir = os.getcwd() os.chdir(testdir) aaa.register('foo', 'pwd', 'a@a.a') os.chdir(old_dir) assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_1(): c = aaa.mailer._parse_smtp_url('') assert c['proto'] == 'smtp' assert c['user'] == None assert c['pass'] == None assert c['fqdn'] == '' assert c['port'] == 25 @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_2(): c = aaa.mailer._parse_smtp_url('starttls://foo') assert c['proto'] == 'starttls' assert c['user'] == None assert c['pass'] == None assert c['fqdn'] == 'foo' assert c['port'] == 25 @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_3(): c = aaa.mailer._parse_smtp_url('foo:443') assert c['proto'] == 'smtp' assert c['user'] == None assert c['pass'] == None assert c['fqdn'] == 'foo' assert c['port'] == 443 @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_4(): c = aaa.mailer._parse_smtp_url('ssl://user:pass@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == 'user' assert c['pass'] == 'pass' assert c['fqdn'] == 'foo' assert c['port'] == 443 @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_5(): c = aaa.mailer._parse_smtp_url('smtp://smtp.magnet.ie') assert c['proto'] == 'smtp' assert c['user'] == None assert c['pass'] == None assert c['fqdn'] == 'smtp.magnet.ie' assert c['port'] == 25 @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_email_as_username_no_password(): # the username contains an at sign '@' c = aaa.mailer._parse_smtp_url('ssl://us.er@somewhere.net@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == 'us.er@somewhere.net', \ "Username is incorrectly parsed as '%s'" % c['user'] assert c['pass'] == None assert c['fqdn'] == 'foo' assert c['port'] == 443 @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_email_as_username(): # the username contains an at sign '@' c = aaa.mailer._parse_smtp_url('ssl://us.er@somewhere.net:pass@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == 'us.er@somewhere.net', \ "Username is incorrectly parsed as '%s'" % c['user'] assert c['pass'] == 'pass' assert c['fqdn'] == 'foo' assert c['port'] == 443 @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_at_sign_in_password(): # the password contains at signs '@' c = aaa.mailer._parse_smtp_url('ssl://username:pass@w@rd@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == 'username', \ "Username is incorrectly parsed as '%s'" % c['user'] assert c['pass'] == 'pass@w@rd', \ "Password is incorrectly parsed as '%s'" % c['pass'] assert c['fqdn'] == 'foo' assert c['port'] == 443 @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_email_as_username_2(): # both the username and the password contains an at sign '@' c = aaa.mailer._parse_smtp_url('ssl://us.er@somewhere.net:pass@word@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == 'us.er@somewhere.net', \ "Username is incorrectly parsed as '%s'" % c['user'] assert c['pass'] == 'pass@word', \ "Password is incorrectly parsed as '%s'" % c['pass'] assert c['fqdn'] == 'foo' assert c['port'] == 443 @raises(RuntimeError) @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_incorrect_URL_port(): c = aaa.mailer._parse_smtp_url(':99999') @raises(RuntimeError) @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_incorrect_URL_port_len(): c = aaa.mailer._parse_smtp_url(':123456') @raises(RuntimeError) @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_incorrect_URL_len(): c = aaa.mailer._parse_smtp_url('a' * 256) @raises(RuntimeError) @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_incorrect_URL_syntax(): c = aaa.mailer._parse_smtp_url('::') @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_IPv4(): c = aaa.mailer._parse_smtp_url('127.0.0.1') assert c['fqdn'] == '127.0.0.1' @with_setup(setup_mockedadmin, teardown_dir) def test_smtp_url_parsing_IPv6(): c = aaa.mailer._parse_smtp_url('[2001:0:0123:4567:89ab:cdef]') assert c['fqdn'] == '[2001:0:0123:4567:89ab:cdef]' # Patch the SMTP class to prevent network interactions @with_setup(setup_mockedadmin, teardown_dir) @mock.patch('cork.cork.SMTP') def test_send_email_SMTP(SMTP): SMTP.return_value = msession = mock.Mock() # session instance aaa.mailer.send_email('address', ' sbj', 'text') aaa.mailer.join() SMTP.assert_called_once_with('localhost', 25) assert msession.sendmail.call_count == 1 assert msession.quit.call_count == 1 assert len(msession.method_calls) == 2 # Patch the SMTP_SSL class to prevent network interactions @with_setup(setup_mockedadmin, teardown_dir) @mock.patch('cork.cork.SMTP_SSL') def test_send_email_SMTP_SSL(SMTP_SSL): SMTP_SSL.return_value = msession = mock.Mock() # session instance aaa.mailer._conf['proto'] = 'ssl' aaa.mailer.send_email('address', ' sbj', 'text') aaa.mailer.join() SMTP_SSL.assert_called_once_with('localhost', 25) assert msession.sendmail.call_count == 1 assert msession.quit.call_count == 1 assert len(msession.method_calls) == 2 # Patch the SMTP_SSL class to prevent network interactions @with_setup(setup_mockedadmin, teardown_dir) @mock.patch('cork.cork.SMTP_SSL') def test_send_email_SMTP_SSL_with_login(SMTP_SSL): SMTP_SSL.return_value = msession = mock.Mock() # session instance aaa.mailer._conf['proto'] = 'ssl' aaa.mailer._conf['user'] = 'username' aaa.mailer.send_email('address', ' sbj', 'text') aaa.mailer.join() SMTP_SSL.assert_called_once_with('localhost', 25) assert msession.login.call_count == 1 assert msession.sendmail.call_count == 1 assert msession.quit.call_count == 1 assert len(msession.method_calls) == 3 # Patch the SMTP_SSL class to prevent network interactions @with_setup(setup_mockedadmin, teardown_dir) @mock.patch('cork.cork.SMTP') def test_send_email_SMTP_STARTTLS(SMTP): SMTP.return_value = msession = mock.Mock() # session instance aaa.mailer._conf['proto'] = 'starttls' aaa.mailer.send_email('address', ' sbj', 'text') aaa.mailer.join() SMTP.assert_called_once_with('localhost', 25) assert msession.ehlo.call_count == 2 assert msession.starttls.call_count == 1 assert msession.sendmail.call_count == 1 assert msession.quit.call_count == 1 assert len(msession.method_calls) == 5 @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) def test_do_not_send_email(): aaa.mailer._conf['fqdn'] = None # disable email delivery aaa.mailer.send_email('address', 'sbj', 'text') aaa.mailer.join() @with_setup(setup_mockedadmin, teardown_dir) def test_validate_registration_no_code(): assert_raises(AAAException, aaa.validate_registration, 'not_a_valid_code') # Patch the mailer _send() method to prevent network interactions @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_validate_registration(mocked): # create registration old_dir = os.getcwd() os.chdir(testdir) aaa.register('user_foo', 'pwd', 'a@a.a') os.chdir(old_dir) assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) # get the registration code, and run validate_registration code = aaa._store.pending_registrations.keys()[0] user_data = aaa._store.pending_registrations[code] aaa.validate_registration(code) assert user_data['username'] in aaa._store.users, "Account should have been added" # test login login = aaa.login('user_foo', 'pwd') assert login == True, "Login must succeed" # The registration should have been removed assert len(aaa._store.pending_registrations) == 0, repr(aaa._store.pending_registrations) # Patch the mailer _send() method to prevent network interactions @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_purge_expired_registration(mocked): old_dir = os.getcwd() os.chdir(testdir) aaa.register('foo', 'pwd', 'a@a.a') os.chdir(old_dir) assert len(aaa._store.pending_registrations) == 1, "The registration should" \ " be present" aaa._purge_expired_registrations() assert len(aaa._store.pending_registrations) == 1, "The registration should " \ "be still there" aaa._purge_expired_registrations(exp_time=0) assert len(aaa._store.pending_registrations) == 0, "The registration should " \ "have been removed" # Patch the mailer _send() method to prevent network interactions @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_prevent_double_registration(mocked): # Create two registration requests, then validate them. # The first should succeed, the second one fail as the account has been created. # create first registration old_dir = os.getcwd() os.chdir(testdir) aaa.register('user_foo', 'first_pwd', 'a@a.a') assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) first_registration_code = aaa._store.pending_registrations.keys()[0] # create second registration aaa.register('user_foo', 'second_pwd', 'b@b.b') os.chdir(old_dir) assert len(aaa._store.pending_registrations) == 2, repr(aaa._store.pending_registrations) registration_codes = aaa._store.pending_registrations.keys() if first_registration_code == registration_codes[0]: second_registration_code = registration_codes[1] else: second_registration_code = registration_codes[0] # Only the 'admin' account exists assert len(aaa._store.users) == 1 # Run validate_registration with the first registration aaa.validate_registration(first_registration_code) assert 'user_foo' in aaa._store.users, "Account should have been added" assert len(aaa._store.users) == 2 # After the first registration only one pending registration should be left # The registration having 'a@a.a' email address should be gone assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) pr_code, pr_data = aaa._store.pending_registrations.items()[0] assert pr_data['email_addr'] == 'b@b.b', "Incorrect registration in the datastore" # Logging in using the first login should succeed login = aaa.login('user_foo', 'first_pwd') assert login == True, "Login must succed" assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) # Run validate_registration with the second registration code # The second registration should fail as the user account exists assert_raises(AAAException, aaa.validate_registration, second_registration_code) # test login login = aaa.login('user_foo', 'second_pwd') assert login == False, "Login must fail" @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_send_password_reset_email_no_params(mocked): aaa.send_password_reset_email() @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_send_password_reset_email_incorrect_addr(mocked): aaa.send_password_reset_email(email_addr='incorrect_addr') @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_send_password_reset_email_incorrect_user(mocked): aaa.send_password_reset_email(username='bogus_name') @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_send_password_reset_email_missing_email_addr(mocked): aaa.send_password_reset_email(username='admin') @raises(AuthException) @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_send_password_reset_email_incorrect_pair(mocked): aaa.send_password_reset_email(username='admin', email_addr='incorrect_addr') @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_send_password_reset_email_by_email_addr(mocked): aaa._store.users['admin']['email_addr'] = 'admin@localhost.local' old_dir = os.getcwd() os.chdir(testdir) aaa.send_password_reset_email(email_addr='admin@localhost.local') os.chdir(old_dir) #TODO: add UT @with_setup(setup_mockedadmin, teardown_dir) @mock.patch.object(Mailer, '_send') def test_send_password_reset_email_by_username(mocked): old_dir = os.getcwd() os.chdir(testdir) aaa._store.users['admin']['email_addr'] = 'admin@localhost.local' assert not mocked.called aaa.send_password_reset_email(username='admin') aaa.mailer.join() os.chdir(old_dir) assert mocked.called assert mocked.call_args[0][0] == 'admin@localhost.local' @raises(AuthException) @with_setup(setup_mockedadmin, teardown_dir) def test_perform_password_reset_invalid(): aaa.reset_password('bogus', 'newpassword') @raises(AuthException) @with_setup(setup_mockedadmin, teardown_dir) def test_perform_password_reset_timed_out(): aaa.password_reset_timeout = 0 token = aaa._reset_code('admin', 'admin@localhost.local') aaa.reset_password(token, 'newpassword') @raises(AAAException) @with_setup(setup_mockedadmin, teardown_dir) def test_perform_password_reset_nonexistent_user(): token = aaa._reset_code('admin_bogus', 'admin@localhost.local') aaa.reset_password(token, 'newpassword') # The following test should fail # an user can change the password reset timestamp by b64-decoding the token, # editing the field and b64-encoding it @SkipTest @raises(AuthException) @with_setup(setup_mockedadmin, teardown_dir) def test_perform_password_reset_mangled_timestamp(): token = aaa._reset_code('admin', 'admin@localhost.local') username, email_addr, tstamp, h = b64decode(token).split(':', 3) tstamp = str(int(tstamp) + 100) mangled_token = ':'.join((username, email_addr, tstamp, h)) mangled_token = b64encode(mangled_token) aaa.reset_password(mangled_token, 'newpassword') @raises(AuthException) @with_setup(setup_mockedadmin, teardown_dir) def test_perform_password_reset_mangled_username(): token = aaa._reset_code('admin', 'admin@localhost.local') username, email_addr, tstamp, h = b64decode(token).split(':', 3) username += "mangled_username" mangled_token = ':'.join((username, email_addr, tstamp, h)) mangled_token = b64encode(mangled_token) aaa.reset_password(mangled_token, 'newpassword') @raises(AuthException) @with_setup(setup_mockedadmin, teardown_dir) def test_perform_password_reset_mangled_email(): token = aaa._reset_code('admin', 'admin@localhost.local') username, email_addr, tstamp, h = b64decode(token).split(':', 3) email_addr += "mangled_email" mangled_token = ':'.join((username, email_addr, tstamp, h)) mangled_token = b64encode(mangled_token) aaa.reset_password(mangled_token, 'newpassword') @with_setup(setup_mockedadmin, teardown_dir) def test_perform_password_reset(): old_dir = os.getcwd() os.chdir(testdir) token = aaa._reset_code('admin', 'admin@localhost.local') aaa.reset_password(token, 'newpassword') os.chdir(old_dir) bottle-cork-0.12.0/tests/testutils.py0000644000175000017500000002607712512453671017445 0ustar fedefede00000000000000# -*- coding: utf-8 -* # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Unit testing - utility functions. # import bottle import os import shutil import sys import json import mock import tempfile from base64 import b64encode, b64decode from pytest import raises import pytest from datetime import datetime from cork import Cork, AAAException, AuthException SkipTest = pytest.mark.skipif(True, reason='skipped') cookie_name = None def pick_temp_directory(): """Select a temporary directory for the test files. Set the tmproot global variable. """ if os.environ.get('TRAVIS', False): return tempfile.mkdtemp() if sys.platform == 'linux2': # In-memory filesystem allows faster testing. return tempfile.mkdtemp(dir='/dev/shm') return tempfile.mkdtemp() def purge_temp_directory(test_dir): """Remove the test directory""" assert test_dir shutil.rmtree(test_dir) REDIR = 302 class WebFunctional(object): def __init__(self): self._tmpdir = None self._app = None self._starting_dir = os.getcwd() def populate_conf_directory(self): """Populate a directory with valid configuration files, to be run just once The files are not modified by each test """ self._tmpdir = os.path.join(self._tmproot, "cork_functional_test_source") # only do this once, as advertised if os.path.exists(self._tmpdir): return os.mkdir(self._tmpdir) os.mkdir(self._tmpdir + "/example_conf") cork = Cork(os.path.join(self._tmpdir, "example_conf"), initialize=True) cork._store.roles['admin'] = 100 cork._store.roles['editor'] = 60 cork._store.roles['user'] = 50 cork._store.save_roles() tstamp = str(datetime.utcnow()) username = password = 'admin' cork._store.users[username] = { 'role': 'admin', 'hash': cork._hash(username, password), 'email_addr': username + '@localhost.local', 'desc': username + ' test user', 'creation_date': tstamp } username = password = '' cork._store.users[username] = { 'role': 'user', 'hash': cork._hash(username, password), 'email_addr': username + '@localhost.local', 'desc': username + ' test user', 'creation_date': tstamp } cork._store.save_users() def populate_temp_dir(self): """populate the temporary test dir""" assert self._tmpdir is not None # copy the needed files shutil.copytree( os.path.join(self._starting_dir, 'tests/example_conf'), os.path.join(self._tmpdir, 'example_conf') ) shutil.copytree( os.path.join(self._starting_dir, 'tests/views'), os.path.join(self._tmpdir, 'views') ) print("Test directory set up") def remove_temp_dir(self): p = os.path.join(self._tmproot, 'cork_functional_test_wd') for f in glob.glob('%s*' % p): #shutil.rmtree(f) pass def teardown(self): print("Doing teardown") try: self._app.post('/logout') except: pass # drop the cookie self._app.reset() assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" # drop the cookie self._app.reset() #assert self._app.get('/admin').status != '200 OK' os.chdir(self._starting_dir) self._app.app.options['timeout'] = self._default_timeout self._app = None shutil.rmtree(self._tmpdir) self._tmpdir = None print("Teardown done") def setup(self): # create test dir and populate it using the example files # save the directory where the unit testing has been run if self._starting_dir is None: self._starting_dir = os.getcwd() # create json files to be used by Cork self._tmpdir = pick_temp_directory() assert self._tmpdir is not None self.populate_temp_dir() # change to the temporary test directory # cork relies on this being the current directory os.chdir(self._tmpdir) self.create_app_instance() self._app.reset() print("Reset done") self._default_timeout = self._app.app.options['timeout'] print("Setup completed") # Utility functions def assert_200(self, path, match): """Assert that a page returns 200""" p = self._app.get(path) assert p.status_int == 200, "Status: %d, Location: %s" % \ (p.status_int, p.location) if match is not None: assert match in p.body, "'%s' not found in body: '%s'" % (match, p.body) return p def assert_redirect(self, page, redir_page, post=None): """Assert that a page redirects to another one""" # perform GET or POST if post is None: p = self._app.get(page, status=302) else: assert isinstance(post, dict) p = self._app.post(page, post, status=302) dest = p.location.split(':80/')[-1] dest = "/%s" % dest assert dest == redir_page, "%s redirects to %s instead of %s" % \ (page, dest, redir_page) return p # Tests def test_functional_login(self): assert self._app self._app.get('/admin', status=302) self._app.get('/my_role', status=302) self.login_as_admin() # fetch a page successfully r = self._app.get('/admin') assert r.status_int == 200, repr(r) def test_login_existing_user_none_password(self): p = self._app.post('/login', {'username': 'admin', 'password': None}) assert p.status_int == REDIR, "Redirect expected" assert p.location == 'http://localhost:80/login', \ "Incorrect redirect to %s" % p.location def test_login_nonexistent_user_none_password(self): p = self._app.post('/login', {'username': 'IAmNotHere', 'password': None}) assert p.status_int == REDIR, "Redirect expected" assert p.location == 'http://localhost:80/login', \ "Incorrect redirect to %s" % p.location def test_login_existing_user_empty_password(self): p = self._app.post('/login', {'username': 'admin', 'password': ''}) assert p.status_int == REDIR, "Redirect expected" assert p.location == 'http://localhost:80/login', \ "Incorrect redirect to %s" % p.location def test_login_nonexistent_user_empty_password(self): p = self._app.post('/login', {'username': 'IAmNotHere', 'password': ''}) assert p.status_int == REDIR, "Redirect expected" assert p.location == 'http://localhost:80/login', \ "Incorrect redirect to %s" % p.location def test_login_existing_user_wrong_password(self): p = self._app.post('/login', {'username': 'admin', 'password': 'BogusPassword'}) assert p.status_int == REDIR, "Redirect expected" assert p.location == 'http://localhost:80/login', \ "Incorrect redirect to %s" % p.location def test_functional_login_logout(self): # Incorrect login p = self._app.post('/login', {'username': 'admin', 'password': 'BogusPassword'}) assert p.status_int == REDIR assert p.location == 'http://localhost:80/login', \ "Incorrect redirect to %s" % p.location # log in and get a cookie p = self._app.post('/login', {'username': 'admin', 'password': 'admin'}) assert p.status_int == REDIR assert p.location == 'http://localhost:80/', \ "Incorrect redirect to %s" % p.location self.assert_200('/my_role', 'admin') # fetch a page successfully assert self._app.get('/admin').status_int == 200, "Admin page should be served" # log out assert self._app.get('/logout').status_int == REDIR # drop the cookie self._app.reset() assert self._app.cookies == {}, "The cookie should be gone" # fetch the same page, unsuccessfully assert self._app.get('/admin').status_int == REDIR def test_functional_user_creation_login_deletion(self): assert self._app.cookies == {}, "The cookie should be not set" # Log in as Admin p = self._app.post('/login', {'username': 'admin', 'password': 'admin'}) assert p.status_int == REDIR assert p.location == 'http://localhost:80/', \ "Incorrect redirect to %s" % p.location self.assert_200('/my_role', 'admin') username = 'BrandNewUser' # Delete the user ret = self._app.post('/delete_user', { 'username': username, }) # Create new user password = '42IsTheAnswer' ret = self._app.post('/create_user', { 'username': username, 'password': password, 'role': 'user' }) retj = json.loads(ret.body) assert 'ok' in retj and retj['ok'] == True, "Failed user creation: %s" % \ ret.body # log out assert self._app.get('/logout').status_int == REDIR self._app.reset() assert self._app.cookies == {}, "The cookie should be gone" # Log in as user p = self._app.post('/login', {'username': username, 'password': password}) assert p.status_int == REDIR and p.location == 'http://localhost:80/', \ "Failed user login" # log out assert self._app.get('/logout').status_int == REDIR self._app.reset() assert self._app.cookies == {}, "The cookie should be gone" # Log in as user with empty password p = self._app.post('/login', {'username': username, 'password': ''}) assert p.status_int == REDIR and p.location == 'http://localhost:80/login', \ "User login should fail" assert self._app.cookies == {}, "The cookie should not be set" # Log in as Admin, again p = self._app.post('/login', {'username': 'admin', 'password': 'admin'}) assert p.status_int == REDIR assert p.location == 'http://localhost:80/', \ "Incorrect redirect to %s" % p.location self.assert_200('/my_role', 'admin') # Delete the user ret = self._app.post('/delete_user', { 'username': username, }) retj = json.loads(ret.body) assert 'ok' in retj and retj['ok'] == True, "Failed user deletion: %s" % \ ret.body #def test_functional_user_registration(self): # assert self._app.cookies == {}, "The cookie should be not set" # # # Register new user # username = 'BrandNewUser' # password = '42IsTheAnswer' # ret = self._app.post('/register', { # 'username': username, # 'password': password, # 'email_address': 'test@localhost.local' # }) def test_functional_expiration(self): raise NotImplementedError bottle-cork-0.12.0/tests/test_foo.py0000644000175000017500000000054612506334640017215 0ustar fedefede00000000000000 import pytest @pytest.fixture(params=["merlinux.eu", "mail.python.org"]) def backend(request): print 'called with ', request.param return request.param @pytest.fixture def fixa(backend): return backend @pytest.fixture def fixb(backend): return backend + 'b' def test_1(fixa): assert not fixa def test_2(fixb): assert not fixb bottle-cork-0.12.0/tests/test_webtest_flask.py0000644000175000017500000001003612512453671021265 0ustar fedefede00000000000000# Cork - Authentication module for the Flask web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional test using Json backend # # Requires WebTest http://webtest.pythonpaste.org/ # # Run as: nosetests functional_test.py # from nose import SkipTest from webtest import TestApp from datetime import timedelta import shutil import os import testutils from cork import FlaskCork REDIR = 302 class Test(testutils.WebFunctional): def create_app_instance(self): """create TestApp instance""" assert self._app is None import simple_webapp_flask self._bottle_app = simple_webapp_flask.app self._app = TestApp(self._bottle_app) #simple_webapp_flask.flask.session.secret_key = 'bogus' simple_webapp_flask.SECRET_KEY = 'bogus' print("Test App created") def teardown(self): print("Doing teardown") try: self._app.post('/logout') except: pass # drop the cookie self._app.reset() assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" # drop the cookie self._app.reset() #assert self._app.get('/admin').status != '200 OK' os.chdir(self._starting_dir) self._app.app.options['timeout'] = self._default_timeout self._app = None shutil.rmtree(self._tmpdir) self._tmpdir = None print("Teardown done") def setup(self): # create test dir and populate it using the example files # save the directory where the unit testing has been run if self._starting_dir is None: self._starting_dir = os.getcwd() # create json files to be used by Cork self._tmpdir = testutils.pick_temp_directory() assert self._tmpdir is not None self.populate_temp_dir() # change to the temporary test directory # cork relies on this being the current directory os.chdir(self._tmpdir) self.create_app_instance() self._app.reset() print("Reset done") #self._default_timeout = self._app.app.options['timeout'] self._default_timeout = 30 #FIXME: reset print("Setup completed") def login_as_admin(self): """perform log in""" assert self._app is not None assert 'session' not in self._app.cookies, "Unexpected cookie found" self.assert_200('/login', 'Please insert your credentials') assert 'session' not in self._app.cookies, "Unexpected cookie found" self.assert_redirect('/admin', '/sorry_page') self.assert_200('/user_is_anonymous', 'True') assert 'session' not in self._app.cookies, "Unexpected cookie found" post = {'username': 'admin', 'password': 'admin'} self.assert_redirect('/login', '/', post=post) assert 'session' in self._app.cookies, "Cookie not found" import bottle session = bottle.request.environ.get('beaker.session') print("Session from func. test", repr(session)) self.assert_200('/login', 'Please insert your credentials') p = self._app.get('/admin') assert 'Welcome' in p.body, repr(p) p = self._app.get('/my_role', status=200) assert p.status_int == 200 assert p.body == 'admin', "Sta" print("Login performed") def test_functional_expiration(self): self.login_as_admin() r = self._app.get('/admin') assert r.status_int == 200, repr(r) # change the cookie expiration in order to expire it saved = self._bottle_app.permanent_session_lifetime try: self._bottle_app.permanent_session_lifetime = timedelta(seconds=-1) # change the cookie expiration in order to expire it self._app.app.options['timeout'] = 0 assert self._app.get('/admin').status_int == REDIR, "The cookie should have expired" finally: self._bottle_app.permanent_session_lifetime = saved bottle-cork-0.12.0/tests/conftest.py0000644000175000017500000000554612517131104017215 0ustar fedefede00000000000000 import bottle import os import pytest from cork import Cork @pytest.fixture def chdir_to_tmpdir(tmpdir): # Chdir into the current tmpdir (used by Cork to find email templates) tmpdir.chdir() assert tmpdir.strpath == os.getcwd() @pytest.fixture def templates_dir(tmpdir, chdir_to_tmpdir): # Setup email templates tmpdir.mkdir('views') tmpdir.join('views/registration_email.tpl').write("""Username:{{username}} Email:{{email_addr}} Code:{{registration_code}}""") tmpdir.join('views/password_reset_email.tpl').write("""Username:{{username}} Email:{{email_addr}} Code:{{reset_code}}""") assert tmpdir.join('views/password_reset_email.tpl').exists() tmpdir.mkdir('examples') tmpdir.mkdir('examples/views') tmpdir.join('examples/views/password_reset_email.tpl').write( """Hello {{username}},
You are receiving this email because you requested a password reset on Cork Demo Webapp.

If you wish to complete the password reset please click on:
Confirm

""") return tmpdir @pytest.fixture def mytmpdir(tmpdir): """Setup tmp directory with test files """ tmpdir.mkdir('views') tmpdir.join('users.json').write("""{"admin": {"email_addr": null, "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596"}}""") tmpdir.join('roles.json').write("""{"special": 200, "admin": 100, "user": 50}""") tmpdir.join('register.json').write("""{}""") tmpdir.join('registration_email.tpl').write("""Username:{{username}} Email:{{email_addr}} Code:{{registration_code}}""") tmpdir.join('password_reset_email.tpl').write("""Username:{{username}} Email:{{email_addr}} Code:{{reset_code}}""") return tmpdir # used by test_scrypt.py @pytest.fixture def aaa(mytmpdir): aaa = Cork(mytmpdir, smtp_server='localhost', email_sender='test@localhost') return aaa # mb = self.setup_test_db() # self.aaa = MockedUnauthenticatedCork(backend=mb, # smtp_server='localhost', email_sender='test@localhost') # cookie_name = None # if hasattr(self, 'purge_test_db'): # self.purge_test_db() # # del(self.aaa) # cookie_name = None def assert_is_redirect(e, path): """Check if an HTTPResponse is a redirect. :param path: relative path without leading slash. :type path: str """ assert isinstance(e, bottle.HTTPResponse), "Incorrect exception type passed to assert_is_redirect" assert e.status_code == 302, "HTTPResponse status should be 302 but is '%s'" % e.status redir_location = e.headers['Location'].rsplit('/', 1)[1] assert redir_location == path, "Redirected to %s instead of %s" % (redir_location, path) bottle-cork-0.12.0/tests/test_pbkdf2.py0000644000175000017500000000753212515203757017610 0ustar fedefede00000000000000 import hashlib import binascii import json from cork import Cork import base64 from base64 import b64encode import pytest from beaker import crypto def test_hash_basic_sha1(): dk = hashlib.pbkdf2_hmac('sha1', b'password', b'salt', 100000) assert binascii.hexlify(dk) == b'f5496bb1328184f5228eff393ab4be9ae8fe69e7' def test_hash_basic_sha1_b(): dk = hashlib.pbkdf2_hmac('sha1', b'password', b'salt', 10) assert binascii.hexlify(dk) == b'ae3fe5f5707e07f3e7c117fb885cd052a6fcd77a' def test_hash_basic_sha1_c(): cleartext = b'x' * 32 salt = b'y' * 32 dk = hashlib.pbkdf2_hmac('sha1', b'password', salt, 10) h_old = crypto.generateCryptoKeys(cleartext, salt, 10) assert binascii.hexlify(dk) == b'b10d37a81afb93afa5db8b522dc5cf8d8164f469' assert binascii.hexlify(h_old) == b'b10d37a81afb93afa5db8b522dc5cf8d8164f469' def test_hash(): cleartext = b'x' * 32 salt = b'y' * 32 print(repr(cleartext)) h = hashlib.pbkdf2_hmac('sha1', cleartext, salt, 10) h = binascii.hexlify(h) assert h == b'916c7f92b887a3eace655172bc3ed21fcecd82ef' def test_hash_32(): cleartext = b'x' * 32 salt = b'y' * 32 h = hashlib.pbkdf2_hmac('sha1', cleartext, salt, 10, dklen=32) h = binascii.hexlify(h) assert h == b'916c7f92b887a3eace655172bc3ed21fcecd82ef4b28c12826cd5cada361013f' def test_compare_pbkdf2(): cleartext = b'x' * 32 salt = b'y' * 32 old_h = crypto.generateCryptoKeys(cleartext, salt, 10) h = hashlib.pbkdf2_hmac('sha1', cleartext, salt, 10, dklen=32) assert len(h) == len(old_h) assert h == old_h # Test PBKDF2-based password hashing _hash = Cork._hash_pbkdf2 def _oldhash(username, pwd, salt=None): username = username.encode('utf-8') pwd = pwd.encode('utf-8') cleartext = username + b'\0' + pwd h = crypto.generateCryptoKeys(cleartext, salt, 10) hashed = b'p' + salt + h return b64encode(hashed) def test_password_hashing_PBKDF2(): shash = _hash(u'user_foo', u'bogus_pwd') shash_old = _oldhash(u'user_foo', u'bogus_pwd') assert isinstance(shash, bytes) assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" assert shash == shash_old def test_password_hashing_PBKDF2_known_hash(): salt = b's' * 32 shash = _hash(u'user_foo', u'bogus_pwd', salt=salt) shash_old = _oldhash(u'user_foo', u'bogus_pwd', salt=salt) assert shash == 'cHNzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzax44AxQgK6uD9q1YWxLos1ispCe1Z7T7pOFK1PwdWEs=' assert shash == shash_old def test_password_hashing_PBKDF2_known_hash_2(): salt = b'\0' * 32 shash = _hash(u'user_foo', u'bogus_pwd', salt=salt) shash_old = _oldhash(u'user_foo', u'bogus_pwd', salt=salt) assert shash == 'cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8Uh4pyEOHoRz4j0lDzAmqb7Dvmo8GpeXwiKTDsuYFw=' assert shash == shash_old def test_password_hashing_PBKDF2_known_hash_3(): salt = b'x' * 32 shash = _hash(u'user_foo', u'bogus_pwd', salt=salt) shash_old = _oldhash(u'user_foo', u'bogus_pwd', salt=salt) assert shash == b'cHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4MEaIU5Op97lmvwX5NpVSTBP8jg8OlrN7c2K8K8tnNks=' assert shash == shash_old def test_password_hashing_PBKDF2_incorrect_hash_len(): salt = b'x' * 31 # Incorrect length with pytest.raises(AssertionError): shash = _hash(u'user_foo', u'bogus_pwd', salt=salt) def test_password_hashing_PBKDF2_incorrect_hash_value(): shash = _hash(u'user_foo', u'bogus_pwd') assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" def test_password_hashing_PBKDF2_collision(): salt = b'S' * 32 hash1 = _hash(u'user_foo', u'bogus_pwd', salt=salt) hash2 = _hash(u'user_foobogus', u'_pwd', salt=salt) assert hash1 != hash2, "Hash collision" bottle-cork-0.12.0/tests/test_sqla.py0000644000175000017500000000771312515736435017405 0ustar fedefede00000000000000 from sqlalchemy import create_engine, delete, select, \ Column, ForeignKey, Integer, MetaData, String, Table, Unicode from cork.backends import SqlAlchemyBackend import pytest import os from cork import Cork class MockedSession(object): """Mock Beaker session """ def __init__(self, username=None): self.__username = username self.__saved = False def get(self, k, default): assert k in ('username') if self.__username is None: return default return self.__username def __getitem__(self, k): assert k in ('username') if self.__username is None: raise KeyError() return self.__username def __setitem__(self, k, v): assert k in ('username') self.__username = v self.__saved = False def delete(self): """Used during logout to delete the current session""" self.__username = None def save(self): self.__saved = True #TODO: implement tests around MockedSession __saved class MockedSessionCork(Cork): """Mocked Cork instance where the session is replaced with MockedSession """ @property def _beaker_session(self): return self._mocked_beaker_session def setup_postgresql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ db_name = 'myapp_test' else: db_name = 'cork_functional_test' uri = "postgresql+psycopg2://postgres:@/%s" % db_name mb = SqlAlchemyBackend(uri, initialize=True) # Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb @pytest.fixture def backend(tmpdir, request): return setup_postgresql_db(request) raise Exception() @pytest.fixture def aaa_admin(templates_dir, backend): # Session with an admin user aaa = MockedSessionCork( templates_dir, backend=backend, email_sender='test@localhost', smtp_server='localhost', ) aaa._mocked_beaker_session = MockedSession(username='admin') return aaa def test_write_user_hash_bytes(aaa_admin): username = 'huh' h = b'1234' tstamp = "just a string" assert isinstance(h, type(b'')) aaa_admin._store.users[username] = { 'role': "user", 'hash': h, 'email_addr': "bar", 'desc': "foo", 'creation_date': tstamp, 'last_login': tstamp } #eng = aaa_admin._store._engine #print(eng.execute("SELECT * FROM users").fetchall()[1]) fetched_h = aaa_admin._store.users[username]['hash'] assert isinstance(fetched_h, type(b'')) assert fetched_h == b'1234' def test_write_user_hash_unicode(aaa_admin): username = 'huh' h = u'1234' tstamp = "just a string" aaa_admin._store.users[username] = { 'role': "user", 'hash': h, 'email_addr': "bar", 'desc': "foo", 'creation_date': tstamp, 'last_login': tstamp } fetched_h = aaa_admin._store.users[username]['hash'] eng = aaa_admin._store._engine print(eng.execute("SELECT * FROM users").fetchall()[1]) assert fetched_h == u'1234' bottle-cork-0.12.0/tests/test_set_pass.py0000644000175000017500000002516512520700252020247 0ustar fedefede00000000000000# -*- coding: utf-8 -* # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional testing - test the Cork module against diffent database backends from base64 import b64encode, b64decode from pytest import raises import bottle import mock import os import pytest import time from cork import Cork, AAAException, AuthException from cork.backends import JsonBackend from cork.backends import MongoDBBackend from cork.backends import SQLiteBackend from cork.backends import SqlAlchemyBackend from conftest import assert_is_redirect try: import pymongo pymongo_available = True except ImportError: pymongo_available = False try: import MySQLdb MySQLdb_available = True except ImportError: MySQLdb_available = False ### Mocked classes class MockedSession(object): """Mock Beaker session """ def __init__(self, username=None): self.__username = username self.__saved = False def get(self, k, default): assert k in ('username') if self.__username is None: return default return self.__username def __getitem__(self, k): assert k in ('username') if self.__username is None: raise KeyError() return self.__username def __setitem__(self, k, v): assert k in ('username') self.__username = v self.__saved = False def delete(self): """Used during logout to delete the current session""" self.__username = None def save(self): self.__saved = True #TODO: implement tests around MockedSession __saved class MockedSessionCork(Cork): """Mocked Cork instance where the session is replaced with MockedSession """ @property def _beaker_session(self): return self._mocked_beaker_session ### Fixtures and helpers ## Backends def setup_sqlite_db(request): # in-memory SQLite DB using the SQLiteBackend backend module. b = SQLiteBackend(':memory:', initialize=True) b.connection.executescript(""" INSERT INTO users (username, email_addr, desc, role, hash, creation_date) VALUES ( 'admin', 'admin@localhost.local', 'admin test user', 'admin', 'cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=', '2012-10-28 20:50:26.286723' ); INSERT INTO roles (role, level) VALUES ('special', 200); INSERT INTO roles (role, level) VALUES ('admin', 100); INSERT INTO roles (role, level) VALUES ('editor', 60); INSERT INTO roles (role, level) VALUES ('user', 50); """) return b def setup_json_db(request, tmpdir): # Setup test directory with valid JSON files and return JsonBackend instance tmpdir.join('users.json').write("""{"admin": {"email_addr": "admin@localhost.local", "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596", "last_login": "2012-10-28 20:50:26.286723"}}""") tmpdir.join('roles.json').write("""{"special": 200, "admin": 100, "user": 50, "editor": 60}""") tmpdir.join('register.json').write("""{}""") return JsonBackend(tmpdir) def setup_sqlalchemy_with_sqlite_in_memory_db(request): # Setup an SqlAlchemyBackend backend using an in-memory SQLite DB mb = SqlAlchemyBackend('sqlite:///:memory:', initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb #def purge_test_db(self): # # Purge DB # mb = connect_to_test_db() # mb._drop_all_tables() def setup_mongo_db(request): # FIXME no last_login? t0 = time.time() def timer(s, max_time=None): delta = time.time() - t0 print("%s %f" % (s, delta)) if max_time is not None: assert delta < max_time mb = MongoDBBackend(db_name='cork-functional-test', initialize=True) timer('connect + init') # Purge DB mb.users._coll.drop() mb.roles._coll.drop() mb.pending_registrations._coll.drop() timer('purge') # Create admin mb.users._coll.insert({ "login": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723" }) timer('create') # Create users mb.roles._coll.insert({'role': 'special', 'val': 200}) mb.roles._coll.insert({'role': 'admin', 'val': 100}) mb.roles._coll.insert({'role': 'editor', 'val': 60}) mb.roles._coll.insert({'role': 'user', 'val': 50}) timer('create users') def fin(): mb.users._coll.drop() mb.roles._coll.drop() request.addfinalizer(fin) timer('mongo setup', 8) return mb def setup_mysql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ password = '' db_name = 'myapp_test' else: password = '' db_name = 'cork_functional_test' uri = "mysql://root:%s@localhost/%s" % (password, db_name) mb = SqlAlchemyBackend(uri, initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): return # TODO: fix mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb def setup_postgresql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ db_name = 'myapp_test' else: db_name = 'cork_functional_test' uri = "postgresql+psycopg2://postgres:@/%s" % db_name mb = SqlAlchemyBackend(uri, initialize=True) # Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb ## General fixtures @pytest.fixture(params=[ 'json', 'mongodb', 'mysql', 'postgresql', 'sqlalchemy', 'sqlite', ]) def backend(tmpdir, request): # Create backend instances backend_type = request.param if backend_type == 'json': return setup_json_db(request, tmpdir) if backend_type == 'sqlite': return setup_sqlite_db(request) if backend_type == 'sqlalchemy': return setup_sqlalchemy_with_sqlite_in_memory_db(request) if backend_type == 'mongodb': if not pymongo_available: pytest.skip() return setup_mongo_db(request) if backend_type == 'mysql': if not MySQLdb_available: pytest.skip() return setup_mysql_db(request) if backend_type == 'postgresql': return setup_postgresql_db(request) raise Exception() @pytest.fixture def aaa_unauth(templates_dir, backend): # Session without any authenticated user aaa = MockedSessionCork( templates_dir, backend=backend, smtp_server='localhost', email_sender='test@localhost', ) aaa._mocked_beaker_session = MockedSession() return aaa @pytest.fixture def aaa_admin(templates_dir, backend): # Session with an admin user aaa = MockedSessionCork( templates_dir, backend=backend, email_sender='test@localhost', smtp_server='localhost', ) aaa._mocked_beaker_session = MockedSession(username='admin') return aaa ### Tests def test_verify_password(aaa_admin): salted_hash = b'cFc8Yx4vLF7qNr2mqeH5PEoFX8T4+LJhEcJTxWhTVYLbIa6vI6DiGmYeP6PZ5iCyucnCJKEd5QOBGyDlmWN82b0=' v = aaa_admin._verify_password(u'admin', u'newpwd', salted_hash) assert v is True def test_set_password_directly(aaa_admin): #assert aaa_admin.login(u'admin', u'newpwd') == False user = aaa_admin.user(u'admin') assert user user.update(pwd='newpwd') assert aaa_admin.login(u'admin', u'newpwd') def test_perform_password_reset(aaa_admin): token = aaa_admin._reset_code(u'admin', u'admin@localhost.local') aaa_admin.reset_password(token, u'newpassword') login = aaa_admin.login(u'admin', u'newpassword') assert login == True, "Login must succeed" bottle-cork-0.12.0/tests/test_mysql.py0000644000175000017500000002752212516510331017574 0ustar fedefede00000000000000# -*- coding: utf-8 -* # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional testing - test the Cork module against diffent database backends from base64 import b64encode, b64decode from pytest import raises import bottle import mock import os import pytest import time from cork import Cork, AAAException, AuthException from cork.backends import JsonBackend from cork.backends import MongoDBBackend from cork.backends import SQLiteBackend from cork.backends import SqlAlchemyBackend from conftest import assert_is_redirect try: import pymongo pymongo_available = True except ImportError: pymongo_available = False try: import MySQLdb MySQLdb_available = True except ImportError: MySQLdb_available = False ### Mocked classes class MockedSession(object): """Mock Beaker session """ def __init__(self, username=None): self.__username = username self.__saved = False def get(self, k, default): assert k in ('username') if self.__username is None: return default return self.__username def __getitem__(self, k): assert k in ('username') if self.__username is None: raise KeyError() return self.__username def __setitem__(self, k, v): assert k in ('username') self.__username = v self.__saved = False def delete(self): """Used during logout to delete the current session""" self.__username = None def save(self): self.__saved = True #TODO: implement tests around MockedSession __saved class MockedSessionCork(Cork): """Mocked Cork instance where the session is replaced with MockedSession """ @property def _beaker_session(self): return self._mocked_beaker_session ### Fixtures and helpers ## Backends def setup_sqlite_db(request): # in-memory SQLite DB using the SQLiteBackend backend module. b = SQLiteBackend(':memory:', initialize=True) b.connection.executescript(""" INSERT INTO users (username, email_addr, desc, role, hash, creation_date) VALUES ( 'admin', 'admin@localhost.local', 'admin test user', 'admin', 'cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=', '2012-10-28 20:50:26.286723' ); INSERT INTO roles (role, level) VALUES ('special', 200); INSERT INTO roles (role, level) VALUES ('admin', 100); INSERT INTO roles (role, level) VALUES ('editor', 60); INSERT INTO roles (role, level) VALUES ('user', 50); """) return b def setup_json_db(request, tmpdir): # Setup test directory with valid JSON files and return JsonBackend instance tmpdir.join('users.json').write("""{"admin": {"email_addr": "admin@localhost.local", "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596", "last_login": "2012-10-28 20:50:26.286723"}}""") tmpdir.join('roles.json').write("""{"special": 200, "admin": 100, "user": 50, "editor": 60}""") tmpdir.join('register.json').write("""{}""") return JsonBackend(tmpdir) def setup_sqlalchemy_with_sqlite_in_memory_db(request): # Setup an SqlAlchemyBackend backend using an in-memory SQLite DB mb = SqlAlchemyBackend('sqlite:///:memory:', initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb #def purge_test_db(self): # # Purge DB # mb = connect_to_test_db() # mb._drop_all_tables() def setup_mongo_db(request): # FIXME no last_login? t0 = time.time() def timer(s, max_time=None): delta = time.time() - t0 print("%s %f" % (s, delta)) if max_time is not None: assert delta < max_time mb = MongoDBBackend(db_name='cork-functional-test', initialize=True) timer('connect + init') # Purge DB mb.users._coll.drop() mb.roles._coll.drop() mb.pending_registrations._coll.drop() timer('purge') # Create admin mb.users._coll.insert({ "login": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723" }) timer('create') # Create users mb.roles._coll.insert({'role': 'special', 'val': 200}) mb.roles._coll.insert({'role': 'admin', 'val': 100}) mb.roles._coll.insert({'role': 'editor', 'val': 60}) mb.roles._coll.insert({'role': 'user', 'val': 50}) timer('create users') def fin(): mb.users._coll.drop() mb.roles._coll.drop() request.addfinalizer(fin) timer('mongo setup', 8) return mb def setup_mysql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ password = '' db_name = 'myapp_test' else: password = '' db_name = 'cork_functional_test' uri = "mysql://root:%s@localhost/%s" % (password, db_name) mb = SqlAlchemyBackend(uri, initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): return # TODO: fix mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb def setup_postgresql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ db_name = 'myapp_test' else: db_name = 'cork_functional_test' uri = "postgresql+psycopg2://postgres:@/%s" % db_name mb = SqlAlchemyBackend(uri, initialize=True) # Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb ## General fixtures @pytest.fixture(params=[ 'json', 'mongodb', 'mysql', 'postgresql', 'sqlalchemy', 'sqlite', ]) def backend(tmpdir, request): # Create backend instances backend_type = request.param if backend_type == 'json': return setup_json_db(request, tmpdir) if backend_type == 'sqlite': return setup_sqlite_db(request) if backend_type == 'sqlalchemy': return setup_sqlalchemy_with_sqlite_in_memory_db(request) if backend_type == 'mongodb': pytest.skip() if not pymongo_available: pytest.skip() return setup_mongo_db(request) if backend_type == 'mysql': if not MySQLdb_available: pytest.skip() return setup_mysql_db(request) if backend_type == 'postgresql': return setup_postgresql_db(request) raise Exception() @pytest.fixture def aaa_unauth(templates_dir, backend): # Session without any authenticated user aaa = MockedSessionCork( templates_dir, backend=backend, smtp_server='localhost', email_sender='test@localhost', ) aaa._mocked_beaker_session = MockedSession() return aaa @pytest.fixture def aaa_admin(templates_dir, backend): # Session with an admin user aaa = MockedSessionCork( templates_dir, backend=backend, email_sender='test@localhost', smtp_server='localhost', ) aaa._mocked_beaker_session = MockedSession(username='admin') return aaa def test_create_and_validate_user(aaa_admin): assert len(aaa_admin._store.users) == 1, "Only the admin user should be present" aaa_admin.create_user(u'phil', 'user', u'hunter123') assert len(aaa_admin._store.users) == 2, "Two users should be present" assert 'phil' in aaa_admin._store.users assert aaa_admin._store.users['phil']['role'] == 'user' assert u'phil' in aaa_admin._store.users if hasattr(aaa_admin._store, '_engine'): eng = aaa_admin._store._engine for row in eng.execute("SELECT * FROM users").fetchall(): name = row[0] # py2: unicode with mysql, str with others print(repr(row)) login = aaa_admin.login('phil', 'hunter123') assert login == True, "Login must succeed" assert aaa_admin._beaker_session['username'] == 'phil' def test_create_and_validate_user_unicode(aaa_admin): assert len(aaa_admin._store.users) == 1, "Only the admin user should be present" if False and hasattr(aaa_admin._store, '_engine'): from sqlalchemy import create_engine, delete, select eng = aaa_admin._store._engine query = select(['users'], aaa_admin._store.users._key_col == 'foo') eng.execute(query).fetchall() if u"hiॐ" in aaa_admin._store.users: assert False aaa_admin.create_user(u'phil_åöॐ', 'user', u'neko_猫') if hasattr(aaa_admin._store, '_engine'): eng = aaa_admin._store._engine for row in eng.execute("SELECT * FROM users").fetchall(): name = row[0] # py2: unicode with mysql, str with others assert len(aaa_admin._store.users) == 2, "Two users should be present" assert u'phil_åöॐ' in aaa_admin._store.users assert aaa_admin._store.users[u'phil_åöॐ']['role'] == 'user' login = aaa_admin.login(u'phil_åöॐ', u'neko_猫') assert login == True, "Login must succeed" assert aaa_admin._beaker_session['username'] == u'phil_åöॐ' bottle-cork-0.12.0/tests/test_functional.py0000644000175000017500000007310612520652355020600 0ustar fedefede00000000000000# -*- coding: utf-8 -* # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional testing - test the Cork module against diffent database backends from base64 import b64encode, b64decode from pytest import raises import bottle import mock import os import pytest import time from cork import Cork, AAAException, AuthException from cork.backends import JsonBackend from cork.backends import MongoDBBackend from cork.backends import SQLiteBackend from cork.backends import SqlAlchemyBackend from conftest import assert_is_redirect try: import pymongo pymongo_available = True except ImportError: pymongo_available = False try: import MySQLdb MySQLdb_available = True except ImportError: MySQLdb_available = False ### Mocked classes class MockedSession(object): """Mock Beaker session """ def __init__(self, username=None): self.__username = username self.__saved = False def get(self, k, default): assert k in ('username') if self.__username is None: return default return self.__username def __getitem__(self, k): assert k in ('username') if self.__username is None: raise KeyError() return self.__username def __setitem__(self, k, v): assert k in ('username') self.__username = v self.__saved = False def delete(self): """Used during logout to delete the current session""" self.__username = None def save(self): self.__saved = True #TODO: implement tests around MockedSession __saved class MockedSessionCork(Cork): """Mocked Cork instance where the session is replaced with MockedSession """ @property def _beaker_session(self): return self._mocked_beaker_session ### Fixtures and helpers ## Backends def setup_sqlite_db(request): # in-memory SQLite DB using the SQLiteBackend backend module. b = SQLiteBackend(':memory:', initialize=True) b.connection.executescript(""" INSERT INTO users (username, email_addr, desc, role, hash, creation_date) VALUES ( 'admin', 'admin@localhost.local', 'admin test user', 'admin', 'cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=', '2012-10-28 20:50:26.286723' ); INSERT INTO roles (role, level) VALUES ('special', 200); INSERT INTO roles (role, level) VALUES ('admin', 100); INSERT INTO roles (role, level) VALUES ('editor', 60); INSERT INTO roles (role, level) VALUES ('user', 50); """) return b def setup_json_db(request, tmpdir): # Setup test directory with valid JSON files and return JsonBackend instance tmpdir.join('users.json').write("""{"admin": {"email_addr": "admin@localhost.local", "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596", "last_login": "2012-10-28 20:50:26.286723"}}""") tmpdir.join('roles.json').write("""{"special": 200, "admin": 100, "user": 50, "editor": 60}""") tmpdir.join('register.json').write("""{}""") return JsonBackend(tmpdir) def setup_sqlalchemy_with_sqlite_in_memory_db(request): # Setup an SqlAlchemyBackend backend using an in-memory SQLite DB mb = SqlAlchemyBackend('sqlite:///:memory:', initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb #def purge_test_db(self): # # Purge DB # mb = connect_to_test_db() # mb._drop_all_tables() def setup_mongo_db(request): # FIXME no last_login? t0 = time.time() def timer(s, max_time=None): delta = time.time() - t0 print("%s %f" % (s, delta)) if max_time is not None: assert delta < max_time mb = MongoDBBackend(db_name='cork-functional-test', initialize=True) timer('connect + init') # Purge DB mb.users._coll.drop() mb.roles._coll.drop() mb.pending_registrations._coll.drop() timer('purge') # Create admin mb.users._coll.insert({ "login": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723" }) timer('create') # Create users mb.roles._coll.insert({'role': 'special', 'val': 200}) mb.roles._coll.insert({'role': 'admin', 'val': 100}) mb.roles._coll.insert({'role': 'editor', 'val': 60}) mb.roles._coll.insert({'role': 'user', 'val': 50}) timer('create users') def fin(): mb.users._coll.drop() mb.roles._coll.drop() request.addfinalizer(fin) timer('mongo setup', 8) return mb def setup_mysql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ password = '' db_name = 'myapp_test' else: password = '' db_name = 'cork_functional_test' uri = "mysql://root:%s@localhost/%s" % (password, db_name) mb = SqlAlchemyBackend(uri, initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): return # TODO: fix mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb def setup_postgresql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ db_name = 'myapp_test' else: db_name = 'cork_functional_test' uri = "postgresql+psycopg2://postgres:@/%s" % db_name mb = SqlAlchemyBackend(uri, initialize=True) # Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb ## General fixtures @pytest.fixture(params=[ 'json', 'mongodb', 'mysql', 'postgresql', 'sqlalchemy', 'sqlite', ]) def backend(tmpdir, request): # Create backend instances backend_type = request.param if backend_type == 'json': return setup_json_db(request, tmpdir) if backend_type == 'sqlite': return setup_sqlite_db(request) if backend_type == 'sqlalchemy': return setup_sqlalchemy_with_sqlite_in_memory_db(request) if backend_type == 'mongodb': if not pymongo_available: pytest.skip() return setup_mongo_db(request) if backend_type == 'mysql': if not MySQLdb_available: pytest.skip() return setup_mysql_db(request) if backend_type == 'postgresql': return setup_postgresql_db(request) raise Exception() @pytest.fixture def aaa_unauth(templates_dir, backend): # Session without any authenticated user aaa = MockedSessionCork( templates_dir, backend=backend, smtp_server='localhost', email_sender='test@localhost', ) aaa._mocked_beaker_session = MockedSession() return aaa @pytest.fixture def aaa_admin(templates_dir, backend): # Session with an admin user aaa = MockedSessionCork( templates_dir, backend=backend, email_sender='test@localhost', smtp_server='localhost', ) aaa._mocked_beaker_session = MockedSession(username='admin') return aaa ### Tests ## Unauthenticated user def test_unauth_basic(aaa_unauth): assert aaa_unauth._beaker_session.get('username', None) == None def test_get_current_user_unauth(aaa_unauth): with raises(AAAException): aaa_unauth.current_user['username'] def test_unauth_is_anonymous(aaa_unauth): assert aaa_unauth.user_is_anonymous def test_failing_login_unauth(aaa_unauth): login = aaa_unauth.login('phil', 'hunter123') assert login == False, "Login must fail" assert aaa_unauth._beaker_session.get('username', None) == None ## Logged in as admin def test_mockedadmin(aaa_admin): assert len(aaa_admin._store.users) == 1, len(aaa_admin._store.users) assert 'admin' in aaa_admin._store.users, repr(aaa_admin._store.users) def test_password_hashing(aaa_admin): shash = aaa_admin._hash('user_foo', 'bogus_pwd') assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" assert aaa_admin._verify_password('user_foo', 'bogus_pwd', shash) == True, \ "Hashing verification should succeed" def test_incorrect_password_hashing(aaa_admin): shash = aaa_admin._hash('user_foo', 'bogus_pwd') assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" assert aaa_admin._verify_password('user_foo', '####', shash) == False, \ "Hashing verification should fail" assert aaa_admin._verify_password('###', 'bogus_pwd', shash) == False, \ "Hashing verification should fail" def test_password_hashing_collision(aaa_admin): salt = b'S' * 32 hash1 = aaa_admin._hash(u'user_foo', u'bogus_pwd', salt=salt) hash2 = aaa_admin._hash(u'user_foobogus', u'_pwd', salt=salt) assert hash1 != hash2, "Hash collision" def test_unauth_create_role(aaa_admin): assert len(aaa_admin._store.users) == 1, "Only the admin user should be present" aaa_admin._store.roles['admin'] = 10 # lower admin level assert aaa_admin._store.roles['admin'] == 10, aaa_admin._store.roles['admin'] assert len(aaa_admin._store.users) == 1, "Only the admin user should be present" with raises(AuthException): aaa_admin.create_role('user', 33) def assert_raises(ex, f, *a, **kw): with raises(ex): f(*a, **kw) def test_create_existing_role(aaa_admin): assert_raises(AAAException, aaa_admin.create_role, 'user', 33) def test_access_nonexisting_role(aaa_admin): with raises(KeyError): aaa_admin._store.roles['NotThere'] def test_create_role_with_incorrect_level(aaa_admin): with raises(AAAException): aaa_admin.create_role('new_user', 'not_a_number') def test_create_role(aaa_admin): assert len(aaa_admin._store.roles) == 4, repr(aaa_admin._store.roles) aaa_admin.create_role('user33', 33) assert len(aaa_admin._store.roles) == 5, repr(aaa_admin._store.roles) def test_unauth_delete_role(aaa_admin): aaa_admin._store.roles['admin'] = 10 # lower admin level assert_raises(AuthException, aaa_admin.delete_role, 'user') def test_delete_nonexistent_role(aaa_admin): assert_raises(AAAException, aaa_admin.delete_role, 'user123') def test_create_delete_role(aaa_admin): assert len(aaa_admin._store.roles) == 4, repr(aaa_admin._store.roles) aaa_admin.create_role('user33', 33) assert len(aaa_admin._store.roles) == 5, repr(aaa_admin._store.roles) assert aaa_admin._store.roles['user33'] == 33 aaa_admin.delete_role('user33') assert len(aaa_admin._store.roles) == 4, repr(aaa_admin._store.roles) def test_list_roles(aaa_admin): roles = list(aaa_admin.list_roles()) assert len(roles) == 4, "Incorrect. Users are: %s" % repr(aaa_admin._store.roles) def test_unauth_create_user(aaa_admin): aaa_admin._store.roles['admin'] = 10 # lower admin level assert_raises(AuthException, aaa_admin.create_user, 'phil', 'user', 'hunter123') def test_create_existing_user(aaa_admin): assert_raises(AAAException, aaa_admin.create_user, 'admin', 'admin', 'bogus') def test_create_user_with_wrong_role(aaa_admin): with raises(AAAException): aaa_admin.create_user('admin2', 'nonexistent_role', 'bogus') def test_create_user(aaa_admin): assert len(aaa_admin._store.users) == 1, repr(aaa_admin._store.users) aaa_admin.create_user('phil', 'user', 'user') assert len(aaa_admin._store.users) == 2, repr(aaa_admin._store.users) assert 'phil' in aaa_admin._store.users @pytest.fixture def disable_os_urandom(monkeypatch): monkeypatch.setattr('os.urandom', lambda n: b'9' * n) def test_check_hashing(aaa_admin, disable_os_urandom): h1 = aaa_admin._hash(u'user', u'pwd') assert h1 == b'cDk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5ihNBRY2RYEuI8BWPKndJzD0BTxFOV+hv4Ih9WvRk9Dg=' def test_create_user_check_hashing(aaa_admin, disable_os_urandom): assert len(aaa_admin._store.users) == 1, repr(aaa_admin._store.users) aaa_admin.create_user(u'phil', 'user', u'pwd') assert len(aaa_admin._store.users) == 2, repr(aaa_admin._store.users) assert 'phil' in aaa_admin._store.users h = aaa_admin._store.users['phil']['hash'].encode('ascii') assert h == b'cDk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5REycUvi9EWiRY7kAUwU4vnGD84a0hstdqigKOaNmqBM=' assert h == aaa_admin._hash(u'phil', u'pwd') def test_unauth_delete_user(aaa_admin): aaa_admin._store.roles['admin'] = 10 # lower admin level assert_raises(AuthException, aaa_admin.delete_user, 'phil') def test_delete_nonexistent_user(aaa_admin): assert_raises(AAAException, aaa_admin.delete_user, 'not_an_user') def test_delete_user(aaa_admin): assert len(aaa_admin._store.users) == 1, repr(aaa_admin._store.users) aaa_admin.delete_user(u'admin') assert len(aaa_admin._store.users) == 0, repr(aaa_admin._store.users) assert 'admin' not in aaa_admin._store.users def test_list_users(aaa_admin): users = list(aaa_admin.list_users()) assert len(users) == 1, "Incorrect. Users are: %s" % repr(aaa_admin._store.users) def test_iteritems_on_users(aaa_admin): expected_dkeys = set(('hash', 'email_addr', 'role', 'creation_date', 'desc', 'last_login')) if isinstance(aaa_admin._store, MongoDBBackend): expected_dkeys.discard('last_login') if hasattr(aaa_admin._store.users, 'iteritems'): items = aaa_admin._store.users.iteritems() else: items = iter(aaa_admin._store.users.items()) for k, v in items: dkeys = set(v.keys()) extra = dkeys - expected_dkeys assert not extra, "Unexpected extra keys: %s" % repr(extra) missing = expected_dkeys - dkeys assert not missing, "Missing keys: %s" % repr(missing) def test_failing_login(aaa_admin): login = aaa_admin.login('phil', 'hunter123') assert login == False, "Login must fail" assert aaa_admin._beaker_session.get('username', None) == 'admin' def test_login_nonexistent_user_empty_password(aaa_admin): login = aaa_admin.login('IAmNotHome', '') assert login == False, "Login must fail" assert aaa_admin._beaker_session.get('username', None) == 'admin' def test_login_existing_user_empty_password(aaa_admin): aaa_admin.create_user('phil', 'user', 'hunter123') assert 'phil' in aaa_admin._store.users assert aaa_admin._store.users['phil']['role'] == 'user' login = aaa_admin.login('phil', '') assert login == False, "Login must fail" assert aaa_admin._beaker_session.get('username', None) == 'admin' def test_create_and_validate_user(aaa_admin): assert len(aaa_admin._store.users) == 1, "Only the admin user should be present" aaa_admin.create_user('phil', 'user', 'hunter123') assert len(aaa_admin._store.users) == 2, "Two users should be present" assert 'phil' in aaa_admin._store.users assert aaa_admin._store.users['phil']['role'] == 'user' login = aaa_admin.login('phil', 'hunter123') assert login == True, "Login must succeed" assert aaa_admin._beaker_session['username'] == 'phil' def test_create_and_validate_user_unicode(aaa_admin, backend): # FIXME, see #4 if hasattr(backend, '_engine'): url = backend._engine.url if str(url).startswith('mysql'): pytest.xfail() assert len(aaa_admin._store.users) == 1, "Only the admin user should be present" aaa_admin.create_user(u'phil_åöॐ', 'user', u'neko_猫') assert len(aaa_admin._store.users) == 2, "Two users should be present" assert u'phil_åöॐ' in aaa_admin._store.users assert aaa_admin._store.users[u'phil_åöॐ']['role'] == 'user' login = aaa_admin.login(u'phil_åöॐ', u'neko_猫') assert login == True, "Login must succeed" assert aaa_admin._beaker_session['username'] == u'phil_åöॐ' def test_create_user_login_logout(aaa_admin): assert 'phil' not in aaa_admin._store.users aaa_admin.create_user('phil', 'user', 'hunter123') assert 'phil' in aaa_admin._store.users login = aaa_admin.login('phil', 'hunter123') assert login == True, "Login must succeed" assert aaa_admin._beaker_session['username'] == 'phil' try: aaa_admin.logout(fail_redirect='/failed_logout') except bottle.HTTPResponse as e: assert_is_redirect(e, 'login') assert aaa_admin._beaker_session.get('username', None) == None def test_modify_user_using_overwrite(aaa_admin): aaa_admin.create_user('phil', 'user', 'hunter123') assert 'phil' in aaa_admin._store.users u = aaa_admin._store.users['phil'] u.update(role='editor') aaa_admin._store.users['phil'] = u assert aaa_admin._store.users['phil']['role'] == 'editor' def test_modify_user(aaa_admin): aaa_admin.create_user('phil', 'user', 'hunter123') aaa_admin._store.users['phil']['role'] = 'editor' assert aaa_admin._store.users['phil']['role'] == 'editor', aaa_admin._store.users['phil'] def test_modify_user_using_local_change(aaa_admin): aaa_admin.create_user('phil', 'user', 'hunter123') u = aaa_admin._store.users['phil'] u['role'] = 'editor' assert u['role'] == 'editor', repr(u) assert aaa_admin._store.users['phil']['role'] == 'editor' def test_write_user_hash_bytes(aaa_admin, backend): username = 'huh' h = b'1234' tstamp = "just a string" h = h.decode('ascii') assert isinstance(h, type(u'')) aaa_admin._store.users[username] = { 'role': "user", 'hash': h, 'email_addr': "bar", 'desc': "foo", 'creation_date': tstamp, 'last_login': tstamp } if hasattr(backend, '_engine'): h_from_db = backend._engine.execute("SELECT * FROM users").fetchall()[1][2] assert h_from_db == '1234' fetched_h = aaa_admin._store.users[username]['hash'] fetched_h = fetched_h.encode('ascii') assert isinstance(fetched_h, type(b'')) assert fetched_h == b'1234' def test_write_user_hash_unicode(aaa_admin): username = 'huh' h = u'1234' tstamp = "just a string" aaa_admin._store.users[username] = { 'role': "user", 'hash': h, 'email_addr': "bar", 'desc': "foo", 'creation_date': tstamp, 'last_login': tstamp } if hasattr(backend, '_engine'): h_from_db = backend._engine.execute("SELECT * FROM users").fetchall()[1][2] print("H %r" % h_from_db) assert h_from_db == '1234' fetched_h = aaa_admin._store.users[username]['hash'] assert fetched_h == u'1234' def test_require_failing_username(aaa_admin): # The user exists, but I'm 'admin' aaa_admin.create_user('phil', 'user', 'hunter123') assert_raises(AuthException, aaa_admin.require, username='phil') def test_require_nonexistent_username(aaa_admin): assert_raises(AAAException, aaa_admin.require, username='no_such_user') def test_require_failing_role_fixed(aaa_admin): assert_raises(AuthException, aaa_admin.require, role='user', fixed_role=True) def test_require_missing_parameter(aaa_admin): with raises(AAAException): aaa_admin.require(fixed_role=True) def test_require_nonexistent_role(aaa_admin): assert_raises(AAAException, aaa_admin.require, role='clown') def test_require_failing_role(aaa_admin): # Requesting level >= 100 assert_raises(AuthException, aaa_admin.require, role='special') def test_successful_require_role(aaa_admin): aaa_admin.require(username='admin') aaa_admin.require(username='admin', role='admin') aaa_admin.require(username='admin', role='admin', fixed_role=True) aaa_admin.require(username='admin', role='user') def test_authenticated_is_not_anonymous(aaa_admin): assert not aaa_admin.user_is_anonymous def test_update_nonexistent_role(aaa_admin): assert_raises(AAAException, aaa_admin.current_user.update, role='clown') def test_update_nonexistent_user(aaa_admin): with raises(AAAException): aaa_admin._store.users.pop(u'admin') aaa_admin.current_user.update(role='user') def test_update_role(aaa_admin): aaa_admin.current_user.update(role='user') assert aaa_admin._store.users['admin']['role'] == 'user' def test_update_pwd(aaa_admin): aaa_admin.current_user.update(pwd='meow') def test_update_email(aaa_admin): aaa_admin.current_user.update(email_addr='foo') assert aaa_admin._store.users['admin']['email_addr'] == 'foo', aaa_admin._store.users['admin'] def test_get_current_user_nonexistent(aaa_admin): # The current user 'admin' is not in the user table with raises(AuthException): aaa_admin._store.users.pop(u'admin') aaa_admin.current_user def test_get_nonexistent_user(aaa_admin): assert aaa_admin.user('nonexistent_user') is None def test_get_user_description_field(aaa_admin): admin = aaa_admin.user(u'admin') for field in ['description', 'email_addr']: assert field in admin.__dict__ def test_register_no_user(aaa_admin): assert_raises(AssertionError, aaa_admin.register, None, 'pwd', 'a@a.a') def test_register_no_pwd(aaa_admin): assert_raises(AssertionError, aaa_admin.register, 'foo', None, 'a@a.a') def test_register_no_email(aaa_admin): assert_raises(AssertionError, aaa_admin.register, 'foo', 'pwd', None) def test_register_already_existing(aaa_admin): assert_raises(AAAException, aaa_admin.register, 'admin', 'pwd', 'a@a.a') def test_register_no_role(aaa_admin): assert_raises(AAAException, aaa_admin.register, 'foo', 'pwd', 'a@a.a', role='clown') def test_register_role_too_high(aaa_admin): assert_raises(AAAException, aaa_admin.register, 'foo', 'pwd', 'a@a.a', role='admin') def test_register_valid(aaa_admin, templates_dir): aaa_admin.mailer.send_email = mock.Mock() aaa_admin.register('foo', 'pwd', 'email@email.org', role='user', email_template='views/registration_email.tpl' ) assert aaa_admin.mailer.send_email.called r = aaa_admin._store.pending_registrations assert len(r) == 1 reg_code = list(r)[0] assert r[reg_code]['username'] == 'foo' assert r[reg_code]['email_addr'] == 'email@email.org' assert r[reg_code]['role'] == 'user' def test_validate_registration_no_code(aaa_admin): assert_raises(AAAException, aaa_admin.validate_registration, 'not_a_valid_code') def test_validate_registration(aaa_admin, templates_dir): aaa_admin.mailer.send_email = mock.Mock() aaa_admin.register('foo', 'pwd', 'email@email.org', role='user', email_template='views/registration_email.tpl' ) r = aaa_admin._store.pending_registrations reg_code = list(r)[0] assert len(aaa_admin._store.users) == 1, "Only the admin user should be present" aaa_admin.validate_registration(reg_code) assert len(aaa_admin._store.users) == 2, "The new user should be present" assert len(aaa_admin._store.pending_registrations) == 0, \ "The registration entry should be removed" def test_send_password_reset_email_no_data(aaa_admin): with raises(AAAException): aaa_admin.send_password_reset_email() def test_send_password_reset_email_incorrect_data(aaa_admin): with raises(AAAException): aaa_admin.send_password_reset_email(username='NotThere', email_addr='NoEmail') def test_send_password_reset_email_incorrect_data2(aaa_admin): with raises(AAAException): # The username is valid but the email address is not matching aaa_admin.send_password_reset_email(username='admin', email_addr='NoEmail') def test_send_password_reset_email_only_incorrect_email(aaa_admin): with raises(AAAException): aaa_admin.send_password_reset_email(email_addr='NoEmail') def test_send_password_reset_email_only_incorrect_username(aaa_admin): with raises(AAAException): aaa_admin.send_password_reset_email(username='NotThere') def test_send_password_reset_email_only_email(aaa_admin, templates_dir): aaa_admin.mailer.send_email = mock.Mock() aaa_admin.send_password_reset_email(email_addr='admin@localhost.local', email_template='views/password_reset_email') def test_send_password_reset_email_only_username(aaa_admin, tmpdir, templates_dir): aaa_admin.mailer.send_email = mock.Mock() aaa_admin.send_password_reset_email(username='admin', email_template='views/password_reset_email') def test_perform_password_reset_invalid(aaa_admin): with raises(AuthException): aaa_admin.reset_password(u'bogus', u'newpassword') def test_perform_password_reset_timed_out(aaa_admin): aaa_admin.password_reset_timeout = 0 token = aaa_admin._reset_code(u'admin', u'admin@localhost.local') with raises(AuthException): aaa_admin.reset_password(token, 'newpassword') def test_perform_password_reset_nonexistent_user(aaa_admin): token = aaa_admin._reset_code(u'admin_bogus', u'admin@localhost.local') with raises(AAAException): aaa_admin.reset_password(token, u'newpassword') # The following test should fail # an user can change the password reset timestamp by b64-decoding the token, # editing the field and b64-encoding it @pytest.mark.xfail # FIXME def test_perform_password_reset_mangled_timestamp(aaa_admin): token = aaa_admin._reset_code(u'admin', 'admin@localhost.local') reset_code = b64decode(token).decode('utf-8') username, email_addr, tstamp, h = reset_code.split(':', 3) tstamp = str(int(tstamp) + 100) mangled_token = ':'.join((username, email_addr, tstamp, h)) mangled_token = b64encode(mangled_token.encode('utf-8')) with raises(AuthException): aaa_admin.reset_password(mangled_token, u'newpassword') def test_perform_password_reset_mangled_username(aaa_admin): token = aaa_admin._reset_code(u'admin', u'admin@localhost.local') reset_code = b64decode(token).decode('utf-8') username, email_addr, tstamp, h = reset_code.split(':', 3) username += "mangled_username" mangled_token = ':'.join((username, email_addr, tstamp, h)) mangled_token = b64encode(mangled_token.encode('utf-8')) with raises(AuthException): aaa_admin.reset_password(mangled_token, u'newpassword') def test_perform_password_reset_mangled_email(aaa_admin): token = aaa_admin._reset_code(u'admin', u'admin@localhost.local') reset_code = b64decode(token).decode('utf-8') username, email_addr, tstamp, h = reset_code.split(':', 3) email_addr += "mangled_email" mangled_token = ':'.join((username, email_addr, tstamp, h)) mangled_token = b64encode(mangled_token.encode('utf-8')) with raises(AuthException): aaa_admin.reset_password(mangled_token, u'newpassword') def test_set_password_directly(aaa_admin): #assert aaa_admin.login(u'admin', u'newpwd') == False user = aaa_admin.user(u'admin') assert user user.update(pwd='newpwd') assert aaa_admin.login(u'admin', u'newpwd') def test_perform_password_reset(aaa_admin): token = aaa_admin._reset_code(u'admin', u'admin@localhost.local') aaa_admin.reset_password(token, u'newpassword') login = aaa_admin.login(u'admin', u'newpassword') assert login == True, "Login must succeed" bottle-cork-0.12.0/tests/test_webtest_decorated.py0000644000175000017500000000474112512453671022125 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional test for decorators-based webapp using Json backend # # Requires WebTest http://webtest.pythonpaste.org/ # # Run as: nosetests tests/test_functional_decorated.py # from nose import SkipTest from webtest import TestApp import shutil import os import testutils from cork import Cork REDIR = 302 class Test(testutils.WebFunctional): def create_app_instance(self): """create TestApp instance""" assert self._app is None import simple_webapp_decorated self._bottle_app = simple_webapp_decorated.app env = {'REMOTE_ADDR': '127.0.0.1'} self._app = TestApp(self._bottle_app, extra_environ=env) print("Test App created") def login_as_admin(self): """perform log in""" assert self._app is not None assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" self.assert_200('/login', 'Please insert your credentials') assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" self.assert_redirect('/admin', '/sorry_page') self.assert_200('/user_is_anonymous', 'True') assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" post = {'username': 'admin', 'password': 'admin'} self.assert_redirect('/login', '/', post=post) assert 'beaker.session.id' in self._app.cookies, "Cookie not found" self.assert_200('/my_role', 'admin') assert 'beaker.session.id' in self._app.cookies, "Cookie not found" import bottle session = bottle.request.environ.get('beaker.session') print("Session from func. test", repr(session)) self.assert_200('/login', 'Please insert your credentials') p = self._app.get('/admin') assert 'Welcome' in p.body, repr(p) p = self._app.get('/my_role', status=200) assert p.status_int == 200 assert p.body == 'admin', "Sta" print("Login performed") def test_functional_expiration(self): self.login_as_admin() r = self._app.get('/admin') assert r.status == '200 OK', repr(r) # change the cookie expiration in order to expire it self._app.app.options['timeout'] = 0 assert self._app.get('/admin').status_int == REDIR, "The cookie should have expired" bottle-cork-0.12.0/tests/simple_webapp.py0000777000175000017500000000000012512453671025447 2../examples/simple_webapp.pyustar fedefede00000000000000bottle-cork-0.12.0/tests/test_sqlalchemy.py0000644000175000017500000003260112515755510020574 0ustar fedefede00000000000000# -*- coding: utf-8 -* # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional testing - test the Cork module against diffent database backends from base64 import b64encode, b64decode from pytest import raises import bottle import mock import os import pytest import time from cork import Cork, AAAException, AuthException from cork.backends import JsonBackend from cork.backends import MongoDBBackend from cork.backends import SQLiteBackend from cork.backends import SqlAlchemyBackend from conftest import assert_is_redirect import MySQLdb ### Mocked classes class MockedSession(object): """Mock Beaker session """ def __init__(self, username=None): self.__username = username self.__saved = False def get(self, k, default): assert k in ('username') if self.__username is None: return default return self.__username def __getitem__(self, k): assert k in ('username') if self.__username is None: raise KeyError() return self.__username def __setitem__(self, k, v): assert k in ('username') self.__username = v self.__saved = False def delete(self): """Used during logout to delete the current session""" self.__username = None def save(self): self.__saved = True #TODO: implement tests around MockedSession __saved class MockedSessionCork(Cork): """Mocked Cork instance where the session is replaced with MockedSession """ @property def _beaker_session(self): return self._mocked_beaker_session ### Fixtures and helpers ## Backends def setup_sqlite_db(request): # in-memory SQLite DB using the SQLiteBackend backend module. b = SQLiteBackend(':memory:', initialize=True) b.connection.executescript(""" INSERT INTO users (username, email_addr, desc, role, hash, creation_date) VALUES ( 'admin', 'admin@localhost.local', 'admin test user', 'admin', 'cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=', '2012-10-28 20:50:26.286723' ); INSERT INTO roles (role, level) VALUES ('special', 200); INSERT INTO roles (role, level) VALUES ('admin', 100); INSERT INTO roles (role, level) VALUES ('editor', 60); INSERT INTO roles (role, level) VALUES ('user', 50); """) return b def setup_json_db(request, tmpdir): # Setup test directory with valid JSON files and return JsonBackend instance tmpdir.join('users.json').write("""{"admin": {"email_addr": "admin@localhost.local", "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596", "last_login": "2012-10-28 20:50:26.286723"}}""") tmpdir.join('roles.json').write("""{"special": 200, "admin": 100, "user": 50, "editor": 60}""") tmpdir.join('register.json').write("""{}""") return JsonBackend(tmpdir) def setup_sqlalchemy_with_sqlite_in_memory_db(request): # Setup an SqlAlchemyBackend backend using an in-memory SQLite DB mb = SqlAlchemyBackend('sqlite:///:memory:', initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb #def purge_test_db(self): # # Purge DB # mb = connect_to_test_db() # mb._drop_all_tables() def setup_mongo_db(request): # FIXME no last_login? t0 = time.time() def timer(s, max_time=None): delta = time.time() - t0 print("%s %f" % (s, delta)) if max_time is not None: assert delta < max_time mb = MongoDBBackend(db_name='cork-functional-test', initialize=True) timer('connect + init') # Purge DB mb.users._coll.drop() mb.roles._coll.drop() mb.pending_registrations._coll.drop() timer('purge') # Create admin mb.users._coll.insert({ "login": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723" }) timer('create') # Create users mb.roles._coll.insert({'role': 'special', 'val': 200}) mb.roles._coll.insert({'role': 'admin', 'val': 100}) mb.roles._coll.insert({'role': 'editor', 'val': 60}) mb.roles._coll.insert({'role': 'user', 'val': 50}) timer('create users') def fin(): mb.users._coll.drop() mb.roles._coll.drop() request.addfinalizer(fin) timer('mongo setup', 8) return mb def setup_mysql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ password = '' db_name = 'myapp_test' else: password = '' db_name = 'cork_functional_test' uri = "mysql://root:%s@localhost/%s" % (password, db_name) mb = SqlAlchemyBackend(uri, initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): return # TODO: fix mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb def setup_postgresql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ db_name = 'myapp_test' else: db_name = 'cork_functional_test' uri = "postgresql+psycopg2://postgres:@/%s" % db_name mb = SqlAlchemyBackend(uri, initialize=True) # Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb ## General fixtures @pytest.fixture(params=[ 'json', 'mysql', 'postgresql', 'sqlalchemy', 'sqlite', ]) def backend(tmpdir, request): # Create backend instances backend_type = request.param if backend_type == 'json': return setup_json_db(request, tmpdir) if backend_type == 'sqlite': return setup_sqlite_db(request) if backend_type == 'sqlalchemy': return setup_sqlalchemy_with_sqlite_in_memory_db(request) if backend_type == 'mysql': return setup_mysql_db(request) if backend_type == 'postgresql': return setup_postgresql_db(request) raise Exception() @pytest.fixture def aaa_unauth(templates_dir, backend): # Session without any authenticated user aaa = MockedSessionCork( templates_dir, backend=backend, smtp_server='localhost', email_sender='test@localhost', ) aaa._mocked_beaker_session = MockedSession() return aaa @pytest.fixture def aaa_admin(templates_dir, backend): # Session with an admin user aaa = MockedSessionCork( templates_dir, backend=backend, email_sender='test@localhost', smtp_server='localhost', ) aaa._mocked_beaker_session = MockedSession(username='admin') return aaa ### Tests def test_password_hashing(aaa_admin): shash = aaa_admin._hash('user_foo', 'bogus_pwd') assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" assert aaa_admin._verify_password('user_foo', 'bogus_pwd', shash) == True, \ "Hashing verification should succeed" def test_incorrect_password_hashing(aaa_admin): shash = aaa_admin._hash('user_foo', 'bogus_pwd') assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" assert aaa_admin._verify_password('user_foo', '####', shash) == False, \ "Hashing verification should fail" assert aaa_admin._verify_password('###', 'bogus_pwd', shash) == False, \ "Hashing verification should fail" def test_password_hashing_collision(aaa_admin): salt = b'S' * 32 hash1 = aaa_admin._hash(u'user_foo', u'bogus_pwd', salt=salt) hash2 = aaa_admin._hash(u'user_foobogus', u'_pwd', salt=salt) assert hash1 != hash2, "Hash collision" @pytest.fixture def disable_os_urandom(monkeypatch): monkeypatch.setattr('os.urandom', lambda n: b'9' * n) def test_check_hashing(aaa_admin, disable_os_urandom): h1 = aaa_admin._hash(u'user', u'pwd') assert h1 == b'cDk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5ihNBRY2RYEuI8BWPKndJzD0BTxFOV+hv4Ih9WvRk9Dg=' def test_create_user_check_hashing(aaa_admin, disable_os_urandom): assert len(aaa_admin._store.users) == 1, repr(aaa_admin._store.users) aaa_admin.create_user(u'phil', 'user', u'pwd') assert len(aaa_admin._store.users) == 2, repr(aaa_admin._store.users) assert 'phil' in aaa_admin._store.users h = aaa_admin._store.users['phil']['hash'].encode('ascii') assert h == b'cDk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5REycUvi9EWiRY7kAUwU4vnGD84a0hstdqigKOaNmqBM=' assert h == aaa_admin._hash(u'phil', u'pwd') def test_write_user_hash_bytes(aaa_admin, backend): username = 'huh' h = b'1234' tstamp = "just a string" h = h.decode('ascii') assert isinstance(h, type(u'')) aaa_admin._store.users[username] = { 'role': "user", 'hash': h, 'email_addr': "bar", 'desc': "foo", 'creation_date': tstamp, 'last_login': tstamp } if hasattr(backend, '_engine'): h_from_db = backend._engine.execute("SELECT * FROM users").fetchall()[1][2] print("H %r" % h_from_db) assert h_from_db == '1234' fetched_h = aaa_admin._store.users[username]['hash'] fetched_h = fetched_h.encode('ascii') assert isinstance(fetched_h, type(b'')) assert fetched_h == b'1234' def test_write_user_hash_unicode(aaa_admin): username = 'huh' h = u'1234' tstamp = "just a string" aaa_admin._store.users[username] = { 'role': "user", 'hash': h, 'email_addr': "bar", 'desc': "foo", 'creation_date': tstamp, 'last_login': tstamp } if hasattr(backend, '_engine'): h_from_db = backend._engine.execute("SELECT * FROM users").fetchall()[1][2] print("H %r" % h_from_db) assert h_from_db == '1234' fetched_h = aaa_admin._store.users[username]['hash'] assert fetched_h == u'1234' def test_iteritems_on_users(aaa_admin): expected_dkeys = set(('hash', 'email_addr', 'role', 'creation_date', 'desc', 'last_login')) if isinstance(aaa_admin._store, MongoDBBackend): expected_dkeys.discard('last_login') if hasattr(aaa_admin._store.users, 'iteritems'): items = aaa_admin._store.users.iteritems() else: items = iter(aaa_admin._store.users.items()) for k, v in items: dkeys = set(v.keys()) extra = dkeys - expected_dkeys assert not extra, "Unexpected extra keys: %s" % repr(extra) missing = expected_dkeys - dkeys assert not missing, "Missing keys: %s" % repr(missing) bottle-cork-0.12.0/tests/test_pymongo.py0000644000175000017500000002450012517521253020116 0ustar fedefede00000000000000# -*- coding: utf-8 -* # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional testing - test the Cork module against diffent database backends from base64 import b64encode, b64decode from pytest import raises import bottle import mock import os import pytest import time from cork import Cork, AAAException, AuthException from cork.backends import JsonBackend from cork.backends import MongoDBBackend from cork.backends import SQLiteBackend from cork.backends import SqlAlchemyBackend from conftest import assert_is_redirect try: import pymongo pymongo_available = True except ImportError: pymongo_available = False try: import MySQLdb MySQLdb_available = True except ImportError: MySQLdb_available = False ### Mocked classes class MockedSession(object): """Mock Beaker session """ def __init__(self, username=None): self.__username = username self.__saved = False def get(self, k, default): assert k in ('username') if self.__username is None: return default return self.__username def __getitem__(self, k): assert k in ('username') if self.__username is None: raise KeyError() return self.__username def __setitem__(self, k, v): assert k in ('username') self.__username = v self.__saved = False def delete(self): """Used during logout to delete the current session""" self.__username = None def save(self): self.__saved = True #TODO: implement tests around MockedSession __saved class MockedSessionCork(Cork): """Mocked Cork instance where the session is replaced with MockedSession """ @property def _beaker_session(self): return self._mocked_beaker_session ### Fixtures and helpers ## Backends def setup_sqlite_db(request): # in-memory SQLite DB using the SQLiteBackend backend module. b = SQLiteBackend(':memory:', initialize=True) b.connection.executescript(""" INSERT INTO users (username, email_addr, desc, role, hash, creation_date) VALUES ( 'admin', 'admin@localhost.local', 'admin test user', 'admin', 'cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=', '2012-10-28 20:50:26.286723' ); INSERT INTO roles (role, level) VALUES ('special', 200); INSERT INTO roles (role, level) VALUES ('admin', 100); INSERT INTO roles (role, level) VALUES ('editor', 60); INSERT INTO roles (role, level) VALUES ('user', 50); """) return b def setup_json_db(request, tmpdir): # Setup test directory with valid JSON files and return JsonBackend instance tmpdir.join('users.json').write("""{"admin": {"email_addr": "admin@localhost.local", "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596", "last_login": "2012-10-28 20:50:26.286723"}}""") tmpdir.join('roles.json').write("""{"special": 200, "admin": 100, "user": 50, "editor": 60}""") tmpdir.join('register.json').write("""{}""") return JsonBackend(tmpdir) def setup_sqlalchemy_with_sqlite_in_memory_db(request): # Setup an SqlAlchemyBackend backend using an in-memory SQLite DB mb = SqlAlchemyBackend('sqlite:///:memory:', initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb #def purge_test_db(self): # # Purge DB # mb = connect_to_test_db() # mb._drop_all_tables() def setup_mongo_db(request): # FIXME no last_login? t0 = time.time() def timer(s, max_time=None): delta = time.time() - t0 print("%s %f" % (s, delta)) if max_time is not None: assert delta < max_time mb = MongoDBBackend(db_name='cork-functional-test', initialize=True) timer('connect + init') # Purge DB mb.users._coll.drop() mb.roles._coll.drop() mb.pending_registrations._coll.drop() timer('purge') # Create admin mb.users._coll.insert({ "login": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723" }) timer('create') # Create users mb.roles._coll.insert({'role': 'special', 'val': 200}) mb.roles._coll.insert({'role': 'admin', 'val': 100}) mb.roles._coll.insert({'role': 'editor', 'val': 60}) mb.roles._coll.insert({'role': 'user', 'val': 50}) timer('create users') def fin(): mb.users._coll.drop() mb.roles._coll.drop() request.addfinalizer(fin) timer('mongo setup', 8) return mb def setup_mysql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ password = '' db_name = 'myapp_test' else: password = '' db_name = 'cork_functional_test' uri = "mysql://root:%s@localhost/%s" % (password, db_name) mb = SqlAlchemyBackend(uri, initialize=True) ## Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): return # TODO: fix mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb def setup_postgresql_db(request): if os.environ.get('TRAVIS', False): # Using Travis-CI - https://travis-ci.org/ db_name = 'myapp_test' else: db_name = 'cork_functional_test' uri = "postgresql+psycopg2://postgres:@/%s" % db_name mb = SqlAlchemyBackend(uri, initialize=True) # Purge DB mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 # Create roles mb.roles.insert({'role': 'special', 'level': 200}) mb.roles.insert({'role': 'admin', 'level': 100}) mb.roles.insert({'role': 'editor', 'level': 60}) mb.roles.insert({'role': 'user', 'level': 50}) # Create admin mb.users.insert({ "username": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723", "last_login": "2012-10-28 20:50:26.286723" }) assert len(mb.roles) == 4 assert len(mb.users) == 1 def fin(): mb._drop_all_tables() assert len(mb.roles) == 0 assert len(mb.users) == 0 request.addfinalizer(fin) return mb ## General fixtures @pytest.fixture(params=[ 'json', 'mongodb', 'mysql', 'postgresql', 'sqlalchemy', 'sqlite', ]) def backend(tmpdir, request): # Create backend instances backend_type = request.param if backend_type == 'json': return setup_json_db(request, tmpdir) if backend_type == 'sqlite': return setup_sqlite_db(request) if backend_type == 'sqlalchemy': return setup_sqlalchemy_with_sqlite_in_memory_db(request) if backend_type == 'mongodb': if not pymongo_available: pytest.skip() return setup_mongo_db(request) if backend_type == 'mysql': if not MySQLdb_available: pytest.skip() return setup_mysql_db(request) if backend_type == 'postgresql': pytest.skip() return setup_postgresql_db(request) raise Exception() @pytest.fixture def aaa_unauth(templates_dir, backend): # Session without any authenticated user aaa = MockedSessionCork( templates_dir, backend=backend, smtp_server='localhost', email_sender='test@localhost', ) aaa._mocked_beaker_session = MockedSession() return aaa @pytest.fixture def aaa_admin(templates_dir, backend): # Session with an admin user aaa = MockedSessionCork( templates_dir, backend=backend, email_sender='test@localhost', smtp_server='localhost', ) aaa._mocked_beaker_session = MockedSession(username='admin') return aaa ### Tests def test_update_role(aaa_admin): aaa_admin.current_user.update(role='user') assert aaa_admin._store.users['admin']['role'] == 'user' def test_update_pwd(aaa_admin): aaa_admin.current_user.update(pwd='meow') def test_update_email(aaa_admin): aaa_admin.current_user.update(email_addr='foo') assert aaa_admin._store.users['admin']['email_addr'] == 'foo', aaa_admin._store.users['admin'] bottle-cork-0.12.0/tests/test_crypto.py0000644000175000017500000000117512514234502017744 0ustar fedefede00000000000000 import pytest from beaker import crypto import hashlib def test_beaker_gen_keys(): cleartext = u'user_foo\x00bogus_pwd' salt = u'x' * 32 h = crypto.generateCryptoKeys(cleartext, salt, 10) cl2 = str(cleartext).encode('ascii', 'strict') print('ct %r' % cleartext) print('ct str %r' % str(cleartext)) print('cl2 %r' % cl2) sa2 = str(salt).encode('ascii', 'strict') h2=hashlib.pbkdf2_hmac(hashlib.sha1().name, cl2, sa2, 10, None) print('hlib convert') print(h2) assert h == b'0F\x88S\x93\xa9\xf7\xb9f\xbf\x05\xf96\x95RL\x13\xfc\x8e\x0f\x0e\x96\xb3{sb\xbc+\xcbg6K' def test_2(): pass bottle-cork-0.12.0/tests/test.py0000644000175000017500000004261612517131104016346 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Unit testing # from base64 import b64encode import mock import pytest import smtplib import cork.cork from cork import Cork, JsonBackend, AAAException, AuthException from cork.base_backend import BackendIOException class RoAttrDict(dict): """Read-only attribute-accessed dictionary. Used to mock beaker's session objects """ def __getattr__(self, name): return self[name] class MockedAdminCork(Cork): """Mocked module where the current user is always 'admin'""" @property def _beaker_session(self): return RoAttrDict(username=u'admin') def _setup_cookie(self, username): global cookie_name cookie_name = username class MockedUnauthenticatedCork(Cork): """Mocked module where the current user not set""" @property def _beaker_session(self): return RoAttrDict() def _setup_cookie(self, username): global cookie_name cookie_name = username @pytest.fixture def json_db_dir(tmpdir, templates_dir): """Setup test directory with valid JSON files""" tmpdir.join('users.json').write("""{"admin": {"email_addr": null, "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596"}}""") tmpdir.join('roles.json').write("""{"special": 200, "admin": 100, "user": 50}""") tmpdir.join('register.json').write("""{}""") return tmpdir @pytest.fixture def aaa(json_db_dir): """Setup a MockedAdminCork instance""" aaa = MockedAdminCork(json_db_dir.strpath, smtp_server='localhost', email_sender='test@localhost') aaa.mailer.use_threads = False return aaa @pytest.fixture def aaa_unauth(json_db_dir): """Setup test directory and a MockedAdminCork instance""" aaa = MockedUnauthenticatedCork(json_db_dir.strpath) aaa.mailer.use_threads = False return aaa # Patch SMTP / SMTP_SSL to prevent network interaction @pytest.fixture(autouse=True) def mock_smtp(monkeypatch): m = mock.Mock() monkeypatch.setattr('cork.cork.SMTP', m) return m @pytest.fixture(autouse=True) def mock_smtp_ssl(monkeypatch): m = mock.Mock() m.return_value = mock.Mock() monkeypatch.setattr('cork.cork.SMTP_SSL', m) return m @pytest.fixture def mock_send(monkeypatch): m = mock.Mock() m.return_value = Mock() monkeypatch.setattr(Mailer, '_send', m) return m def raises(f, *e): def wrapper(*a, **kw): return f(*a, **kw) return wrapper # Tests def test_init(json_db_dir): Cork(json_db_dir.strpath) def test_initialize_storage(json_db_dir): jb = JsonBackend(json_db_dir.strpath, initialize=True) Cork(backend=jb) assert json_db_dir.join('users.json').read() == '{}' assert json_db_dir.join('roles.json').read() == '{}' assert json_db_dir.join('register.json').read() == '{}' return with open("%s/views/registration_email.tpl" % testdir) as f: assert f.readlines() == [ 'Username:{{username}} Email:{{email_addr}} Code:{{registration_code}}'] with open("%s/views/password_reset_email.tpl" % testdir) as f: assert f.readlines() == [ 'Username:{{username}} Email:{{email_addr}} Code:{{reset_code}}'] def test_unable_to_save(json_db_dir): bogus_dir = '/___inexisting_directory___' with pytest.raises(BackendIOException): Cork(bogus_dir, initialize=True) def test_loadjson_missing_file(aaa): with pytest.raises(BackendIOException): aaa._store._loadjson('nonexistent_file', {}) def test_loadjson_broken_file(aaa, json_db_dir): json_db_dir.join('broken_file.json').write('-----') with pytest.raises(BackendIOException): aaa._store._loadjson('broken_file', {}) def test_loadjson_unchanged(aaa): # By running _refresh with unchanged files the files should not be reloaded mtimes = aaa._store._mtimes aaa._store._refresh() # The test simply ensures that no mtimes have been updated assert mtimes == aaa._store._mtimes # Test PBKDF2-based password hashing def test_password_hashing_PBKDF2(aaa): shash = aaa._hash(u'user_foo', u'bogus_pwd') assert isinstance(shash, bytes) assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" assert aaa._verify_password('user_foo', 'bogus_pwd', shash) == True, \ "Hashing verification should succeed" def test_hashlib_pbk(): # Hashlib works under py2 and py3 producing the same output. # With iterations = 10 and dklen = 32 the output is also consistent with # beaker under py2 as in the previous versions of Cork import hashlib cleartext = b'hello' salt = b'hi' h = hashlib.pbkdf2_hmac('sha1', cleartext, salt, 10, dklen=32) assert b64encode(h) == b'QTH8vcCFLLqLhxCTnkz6sq+Un3B4RQgWjMPpRC9hfEY=' def test_password_hashing_PBKDF2_known_hash(aaa): assert aaa.preferred_hashing_algorithm == 'PBKDF2' salt = b's' * 32 shash = aaa._hash(u'user_foo', u'bogus_pwd', salt=salt) assert shash == b'cHNzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzax44AxQgK6uD9q1YWxLos1ispCe1Z7T7pOFK1PwdWEs=' def test_password_hashing_PBKDF2_known_hash_2(aaa): assert aaa.preferred_hashing_algorithm == 'PBKDF2' salt = b'\0' * 32 shash = aaa._hash(u'user_foo', u'bogus_pwd', salt=salt) assert shash == b'cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8Uh4pyEOHoRz4j0lDzAmqb7Dvmo8GpeXwiKTDsuYFw=' def test_password_hashing_PBKDF2_known_hash_3(aaa): assert aaa.preferred_hashing_algorithm == 'PBKDF2' salt = b'x' * 32 shash = aaa._hash(u'user_foo', u'bogus_pwd', salt=salt) assert shash == b'cHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4MEaIU5Op97lmvwX5NpVSTBP8jg8OlrN7c2K8K8tnNks=' def test_password_hashing_PBKDF2_incorrect_hash_len(aaa): salt = b'x' * 31 # Incorrect length with pytest.raises(AssertionError): shash = aaa._hash(u'user_foo', u'bogus_pwd', salt=salt) def test_password_hashing_PBKDF2_incorrect_hash_value(aaa): shash = aaa._hash(u'user_foo', u'bogus_pwd') assert len(shash) == 88, "hash length should be 88 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" assert aaa._verify_password(u'user_foo', u'####', shash) == False, \ "Hashing verification should fail" assert aaa._verify_password(u'###', u'bogus_pwd', shash) == False, \ "Hashing verification should fail" def test_password_hashing_PBKDF2_collision(aaa): salt = b'S' * 32 hash1 = aaa._hash(u'user_foo', u'bogus_pwd', salt=salt) hash2 = aaa._hash(u'user_foobogus', u'_pwd', salt=salt) assert hash1 != hash2, "Hash collision" # Test password hashing for inexistent algorithms def test_password_hashing_bogus_algo(aaa): with pytest.raises(RuntimeError): aaa._hash('user_foo', 'bogus_pwd', algo='bogus_algo') def test_password_hashing_bogus_algo_during_verify(aaa): # Incorrect hash type (starts with "X") shash = b64encode(b'X' + b'bogusstring') with pytest.raises(RuntimeError): aaa._verify_password(u'user_foo', u'bogus_pwd', shash) # End of password hashing tests @pytest.mark.xfail def test_create_empty_role(aaa): # TODO: implement empty role check with pytest.raises(AAAException): aaa.create_role('', 42) def test_authenticated_is_not_anonymous(aaa): assert not aaa.user_is_anonymous def test_register(aaa, json_db_dir): aaa.register(u'foo', u'pwd', u'a@a.a') assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) def test_smtp_url_parsing_1(aaa): c = aaa.mailer._parse_smtp_url('') assert c['proto'] == 'smtp' assert c['user'] == None assert c['pass'] == None assert c['fqdn'] == '' assert c['port'] == 25 def test_smtp_url_parsing_2(aaa): c = aaa.mailer._parse_smtp_url('starttls://foo') assert c['proto'] == 'starttls' assert c['user'] == None assert c['pass'] == None assert c['fqdn'] == 'foo' assert c['port'] == 25 def test_smtp_url_parsing_3(aaa): c = aaa.mailer._parse_smtp_url('foo:443') assert c['proto'] == 'smtp' assert c['user'] == None assert c['pass'] == None assert c['fqdn'] == 'foo' assert c['port'] == 443 def test_smtp_url_parsing_4(aaa): c = aaa.mailer._parse_smtp_url('ssl://user:pass@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == 'user' assert c['pass'] == 'pass' assert c['fqdn'] == 'foo' assert c['port'] == 443 def test_smtp_url_parsing_5(aaa): c = aaa.mailer._parse_smtp_url('smtp://smtp.magnet.ie') assert c['proto'] == 'smtp' assert c['user'] == None assert c['pass'] == None assert c['fqdn'] == 'smtp.magnet.ie' assert c['port'] == 25 def test_smtp_url_parsing_email_as_username_no_password(aaa): # the username contains an at sign '@' c = aaa.mailer._parse_smtp_url('ssl://us.er@somewhere.net@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == u'us.er@somewhere.net', \ "Username is incorrectly parsed as '%s'" % c['user'] assert c['pass'] == None assert c['fqdn'] == 'foo' assert c['port'] == 443 def test_smtp_url_parsing_email_as_username(aaa): # the username contains an at sign '@' c = aaa.mailer._parse_smtp_url('ssl://us.er@somewhere.net:pass@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == u'us.er@somewhere.net', \ "Username is incorrectly parsed as '%s'" % c['user'] assert c['pass'] == 'pass' assert c['fqdn'] == 'foo' assert c['port'] == 443 def test_smtp_url_parsing_at_sign_in_password(aaa): # the password contains at signs '@' c = aaa.mailer._parse_smtp_url('ssl://username:pass@w@rd@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == 'username', \ "Username is incorrectly parsed as '%s'" % c['user'] assert c['pass'] == 'pass@w@rd', \ "Password is incorrectly parsed as '%s'" % c['pass'] assert c['fqdn'] == 'foo' assert c['port'] == 443 def test_smtp_url_parsing_email_as_username_2(aaa): # both the username and the password contains an at sign '@' c = aaa.mailer._parse_smtp_url('ssl://us.er@somewhere.net:pass@word@foo:443/') assert c['proto'] == 'ssl' assert c['user'] == u'us.er@somewhere.net', \ "Username is incorrectly parsed as '%s'" % c['user'] assert c['pass'] == u'pass@word', \ "Password is incorrectly parsed as '%s'" % c['pass'] assert c['fqdn'] == 'foo' assert c['port'] == 443 def test_smtp_url_parsing_incorrect_URL_port(aaa): with pytest.raises(RuntimeError): c = aaa.mailer._parse_smtp_url(':99999') def test_smtp_url_parsing_incorrect_URL_port_len(aaa): with pytest.raises(RuntimeError): c = aaa.mailer._parse_smtp_url(':123456') def test_smtp_url_parsing_incorrect_URL_len(aaa): with pytest.raises(RuntimeError): c = aaa.mailer._parse_smtp_url('a' * 256) def test_smtp_url_parsing_incorrect_URL_syntax(aaa): with pytest.raises(RuntimeError): c = aaa.mailer._parse_smtp_url('::') def test_smtp_url_parsing_IPv4(aaa): c = aaa.mailer._parse_smtp_url('127.0.0.1') assert c['fqdn'] == '127.0.0.1' def test_smtp_url_parsing_IPv6(aaa): c = aaa.mailer._parse_smtp_url('[2001:0:0123:4567:89ab:cdef]') assert c['fqdn'] == '[2001:0:0123:4567:89ab:cdef]' def test_send_email_SMTP(aaa, mock_smtp): aaa.mailer.send_email(u'address', u' sbj', u'text') aaa.mailer.join() mock_smtp.assert_called_once_with('localhost', 25) session = mock_smtp.return_value assert session.sendmail.call_count == 1 assert session.quit.call_count == 1 def test_send_email_SMTP_SSL(aaa, mock_smtp_ssl): aaa.mailer._conf['proto'] = 'ssl' aaa.mailer.send_email('address', ' sbj', 'text') aaa.mailer.join() mock_smtp_ssl.assert_called_once_with('localhost', 25) session = mock_smtp_ssl.return_value assert session.sendmail.call_count == 1 assert session.quit.call_count == 1 assert len(session.method_calls) == 2 def test_send_email_SMTP_SSL_with_login(aaa, mock_smtp_ssl): aaa.mailer._conf['proto'] = 'ssl' aaa.mailer._conf['user'] = u'username' aaa.mailer.send_email('address', ' sbj', 'text') aaa.mailer.join() mock_smtp_ssl.assert_called_once_with('localhost', 25) session = mock_smtp_ssl.return_value assert session.login.call_count == 1 assert session.sendmail.call_count == 1 assert session.quit.call_count == 1 assert len(session.method_calls) == 3 def test_send_email_SMTP_STARTTLS(aaa, mock_smtp): aaa.mailer._conf['proto'] = 'starttls' aaa.mailer.send_email(u'address', u' sbj', u'text') aaa.mailer.join() mock_smtp.assert_called_once_with('localhost', 25) session = mock_smtp.return_value assert session.ehlo.call_count == 2 assert session.starttls.call_count == 1 assert session.sendmail.call_count == 1 assert session.quit.call_count == 1 assert len(session.method_calls) == 5 def test_do_not_send_email(aaa): aaa.mailer._conf['fqdn'] = None # disable email delivery with pytest.raises(AAAException): aaa.mailer.send_email(u'address', u'sbj', u'text') aaa.mailer.join() def test_purge_expired_registration(aaa, json_db_dir): aaa.register(u'foo', u'pwd', u'a@a.a') assert len(aaa._store.pending_registrations) == 1, "The registration should" \ " be present" aaa._purge_expired_registrations() assert len(aaa._store.pending_registrations) == 1, "The registration should " \ "be still there" aaa._purge_expired_registrations(exp_time=0) assert len(aaa._store.pending_registrations) == 0, "The registration should " \ "have been removed" def test_prevent_double_registration(aaa, json_db_dir): # Create two registration requests, then validate them. # The first should succeed, the second one fail as the account has been created. # create first registration aaa.register(u'user_foo', u'first_pwd', u'a@a.a') assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) for first_registration_code in aaa._store.pending_registrations: break # create second registration aaa.register(u'user_foo', u'second_pwd', u'b@b.b') assert len(aaa._store.pending_registrations) == 2, repr(aaa._store.pending_registrations) registration_codes = list(aaa._store.pending_registrations) if first_registration_code == registration_codes[0]: second_registration_code = registration_codes[1] else: second_registration_code = registration_codes[0] # Only the 'admin' account exists assert len(aaa._store.users) == 1 # Run validate_registration with the first registration aaa.validate_registration(first_registration_code) assert 'user_foo' in aaa._store.users, "Account should have been added" assert len(aaa._store.users) == 2 # After the first registration only one pending registration should be left # The registration having 'a@a.a' email address should be gone assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) for pr_code, pr_data in aaa._store.pending_registrations.items(): break assert pr_data['email_addr'] == u'b@b.b', "Incorrect registration in the datastore" # Logging in using the first login should succeed login = aaa.login('user_foo', 'first_pwd') assert login == True, "Login must succed" assert len(aaa._store.pending_registrations) == 1, repr(aaa._store.pending_registrations) # Run validate_registration with the second registration code # The second registration should fail as the user account exists with pytest.raises(AAAException): aaa.validate_registration(second_registration_code) # test login login = aaa.login('user_foo', 'second_pwd') assert login == False, "Login must fail" def test_send_password_reset_email_no_params(aaa): with pytest.raises(AAAException): aaa.send_password_reset_email() def test_send_password_reset_email_incorrect_addr(aaa): with pytest.raises(AAAException): aaa.send_password_reset_email(email_addr=u'incorrect_addr') def test_send_password_reset_email_incorrect_user(aaa): with pytest.raises(AAAException): aaa.send_password_reset_email(username=u'bogus_name') def test_send_password_reset_email_missing_email_addr(aaa): with pytest.raises(AAAException): aaa.send_password_reset_email(username=u'admin') def test_send_password_reset_email_incorrect_pair(aaa): with pytest.raises(AuthException): aaa.send_password_reset_email(username=u'admin', email_addr=u'incorrect_addr') def test_send_password_reset_email_by_email_addr(aaa, json_db_dir): aaa._store.users['admin']['email_addr'] = u'admin@localhost.local' aaa.send_password_reset_email(email_addr=u'admin@localhost.local') def test_send_password_reset_email_by_username(aaa, json_db_dir, mock_smtp): aaa._store.users['admin']['email_addr'] = u'admin@localhost.local' assert aaa._store.users['admin']['email_addr'] == u'admin@localhost.local' assert not mock_smtp.called aaa.send_password_reset_email(username='admin') aaa.mailer.join() assert mock_smtp.called session = mock_smtp.return_value assert session.sendmail.called assert session.sendmail.call_args[0][1] == u'admin@localhost.local' assert session.quit.called bottle-cork-0.12.0/tests/test_webtest_bottle.py0000644000175000017500000000435312512453671021463 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional test using Json backend # # Requires WebTest http://webtest.pythonpaste.org/ # # Run as: nosetests functional_test.py # from nose import SkipTest from webtest import TestApp import shutil import os import testutils from cork import Cork REDIR = 302 class Test(testutils.WebFunctional): def create_app_instance(self): """create TestApp instance""" assert self._app is None import simple_webapp self._bottle_app = simple_webapp.app self._app = TestApp(self._bottle_app) print("Test App created") def login_as_admin(self): """perform log in""" assert self._app is not None assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" self.assert_200('/login', 'Please insert your credentials') assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" self.assert_redirect('/admin', '/sorry_page') self.assert_200('/user_is_anonymous', 'True') assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" post = {'username': 'admin', 'password': 'admin'} self.assert_redirect('/login', '/', post=post) assert 'beaker.session.id' in self._app.cookies, "Cookie not found" import bottle session = bottle.request.environ.get('beaker.session') print("Session from func. test", repr(session)) self.assert_200('/login', 'Please insert your credentials') p = self._app.get('/admin') assert 'Welcome' in p.body, repr(p) p = self._app.get('/my_role', status=200) assert p.status_int == 200 assert p.body == 'admin', "Sta" print("Login performed") def test_functional_expiration(self): self.login_as_admin() r = self._app.get('/admin') assert r.status == '200 OK', repr(r) # change the cookie expiration in order to expire it self._app.app.options['timeout'] = 0 assert self._app.get('/admin').status_int == REDIR, "The cookie should have expired" bottle-cork-0.12.0/tests/test_scrypt.py0000644000175000017500000001025512512453671017757 0ustar fedefede00000000000000 # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Unit testing # Test scrypt-based password hashing # from pytest import raises from time import time import os import shutil from cork import Cork, JsonBackend, AuthException import testutils testdir = None # Test directory aaa = None # global Cork instance cookie_name = None # global variable to track cookie status tmproot = testutils.pick_temp_directory() def setup_dir(): """Setup test directory with valid JSON files""" global testdir tstamp = "%f" % time() testdir = "%s/fl_%s" % (tmproot, tstamp) os.mkdir(testdir) os.mkdir(testdir + '/views') with open("%s/users.json" % testdir, 'w') as f: f.write("""{"admin": {"email_addr": null, "desc": null, "role": "admin", "hash": "69f75f38ac3bfd6ac813794f3d8c47acc867adb10b806e8979316ddbf6113999b6052efe4ba95c0fa9f6a568bddf60e8e5572d9254dbf3d533085e9153265623", "creation_date": "2012-04-09 14:22:27.075596"}}""") with open("%s/roles.json" % testdir, 'w') as f: f.write("""{"special": 200, "admin": 100, "user": 50}""") with open("%s/register.json" % testdir, 'w') as f: f.write("""{}""") with open("%s/views/registration_email.tpl" % testdir, 'w') as f: f.write("""Username:{{username}} Email:{{email_addr}} Code:{{registration_code}}""") with open("%s/views/password_reset_email.tpl" % testdir, 'w') as f: f.write("""Username:{{username}} Email:{{email_addr}} Code:{{reset_code}}""") print("setup done in %s" % testdir) def setUp(): global aaa setup_dir() aaa = Cork(testdir, smtp_server='localhost', email_sender='test@localhost') def teardown_dir(): global cookie_name global testdir if testdir: shutil.rmtree(testdir) testdir = None cookie_name = None def tearDown(): global aaa aaa = None teardown_dir() def test_password_hashing_scrypt(aaa): shash = aaa._hash('user_foo', 'bogus_pwd', algo='scrypt') assert len(shash) == 132, "hash length should be 132 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '=': %r" % shash assert aaa._verify_password('user_foo', 'bogus_pwd', shash) == True, \ "Hashing verification should succeed" def test_password_hashing_scrypt_known_hash(aaa): salt = b's' * 32 shash = aaa._hash('user_foo', 'bogus_pwd', salt=salt, algo='scrypt') assert shash == b'c3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3NzeLt/2Ta8vJOVqimNpN9G1WWxN1hxlUOJDPgH+0wqPpG20XQHFHLlksDIUo2BL4P8BMLBZj7F+cq6UP6pc304LQ==', repr(shash) def test_password_hashing_scrypt_known_hash_2(aaa): salt = b'\0' * 32 shash = aaa._hash('user_foo', 'bogus_pwd', salt=salt, algo='scrypt') assert shash == b'cwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmu5jQskr2/yX13Yxmc4TYL0MIuSxwo41SVJwn/QueiDdLGkNaEsxlKL37i98YofXxs8xJJAJlC3Xj/9Nx0RNBw==' def test_password_hashing_scrypt_known_hash_3(aaa): salt = b'x' * 32 shash = aaa._hash('user_foo', 'bogus_pwd', salt=salt, algo='scrypt') assert shash == b'c3h4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4yKuT1e8lovFZnaaOctivIvYBPkLoKDXX72kf5/nRuGIgyyhiKxxKE4LVYFKFCeVNPQM5m/+LulQkWhO0aB89lA==' def test_password_hashing_scrypt_incorrect_hash_len(aaa): salt = b'x' * 31 # Incorrect length with raises(AssertionError): shash = aaa._hash('user_foo', 'bogus_pwd', salt=salt, algo='scrypt') def test_password_hashing_scrypt_incorrect_hash_value(aaa): shash = aaa._hash('user_foo', 'bogus_pwd', algo='scrypt') assert len(shash) == 132, "hash length should be 132 and is %d" % len(shash) assert shash.endswith(b'='), "hash should end with '='" assert aaa._verify_password('user_foo', '####', shash) == False, \ "Hashing verification should fail" assert aaa._verify_password('###', 'bogus_pwd', shash) == False, \ "Hashing verification should fail" def test_password_hashing_scrypt_collision(aaa): salt = b'S' * 32 hash1 = aaa._hash('user_foo', 'bogus_pwd', salt=salt, algo='scrypt') hash2 = aaa._hash('user_foobogus', '_pwd', salt=salt, algo='scrypt') assert hash1 != hash2, "Hash collision" bottle-cork-0.12.0/tests/simple_webapp_flask.py0000755000175000017500000001267012512453671021411 0ustar fedefede00000000000000#!/usr/bin/env python # # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Cork example web application # # The following users are already available: # admin/admin, demo/demo import flask from beaker.middleware import SessionMiddleware from cork import FlaskCork import logging import os logging.basicConfig(format='localhost - - [%(asctime)s] %(message)s', level=logging.DEBUG) log = logging.getLogger(__name__) # Use users.json and roles.json in the local example_conf directory aaa = FlaskCork('example_conf', email_sender='federico.ceratto@gmail.com', smtp_url='smtp://smtp.magnet.ie') app = flask.Flask(__name__) app.debug = True app.options = {} #FIXME from flask import jsonify #session_opts = { # 'session.cookie_expires': True, # 'session.encrypt_key': 'please use a random key and keep it secret!', # 'session.httponly': True, # 'session.timeout': 3600 * 24, # 1 day # 'session.type': 'cookie', # 'session.validate_key': True, #} #app = SessionMiddleware(app, session_opts) # # Bottle methods # # def post_get(name, default=''): v = flask.request.form.get(name, default).strip() return str(v) from cork import Redirect @app.errorhandler(Redirect) def redirect_exception_handler(e): return flask.redirect(e.message) @app.route('/login', methods=['POST']) def login(): """Authenticate users""" username = post_get('username') password = post_get('password') aaa.login(username, password, success_redirect='/', fail_redirect='/login') @app.route('/user_is_anonymous') def user_is_anonymous(): if aaa.user_is_anonymous: return 'True' return 'False' @app.route('/logout') def logout(): aaa.logout(success_redirect='/login') @app.route('/register', methods=['POST']) def register(): """Send out registration email""" aaa.register(post_get('username'), post_get('password'), post_get('email_address')) return 'Please check your mailbox.' @app.route('/validate_registration/:registration_code') def validate_registration(registration_code): """Validate registration, create user account""" aaa.validate_registration(registration_code) return 'Thanks. Go to login' @app.route('/reset_password', methods=['POST']) def send_password_reset_email(): """Send out password reset email""" aaa.send_password_reset_email( username=post_get('username'), email_addr=post_get('email_address') ) return 'Please check your mailbox.' @app.route('/change_password/:reset_code') def change_password(reset_code): """Show password change form""" return flask.render_template('password_change_form', reset_code=reset_code) @app.route('/change_password', methods=['POST']) def do_change_password(): """Change password""" aaa.reset_password(post_get('reset_code'), post_get('password')) return 'Thanks. Go to login' @app.route('/') def index(): """Only authenticated users can see this""" aaa.require(fail_redirect='/login') return 'Welcome! Admin page Logout' @app.route('/restricted_download') def restricted_download(): """Only authenticated users can download this file""" aaa.require(fail_redirect='/login') return flask.static_file('static_file', root='.') @app.route('/my_role') def show_current_user_role(): """Show current user role""" session = flask.request.environ.get('beaker.session') print "Session from simple_webapp", repr(session) aaa.require(fail_redirect='/login') return aaa.current_user.role # Admin-only pages @app.route('/admin') def admin(): """Only admin users can see this""" aaa.require(role='admin', fail_redirect='/sorry_page') return flask.render_template('admin_page.html', current_user=aaa.current_user, users=aaa.list_users(), roles=aaa.list_roles() ) @app.route('/create_user', methods=['POST']) def create_user(): try: aaa.create_user(post_get('username'), post_get('role'), post_get('password')) return jsonify(ok=True, msg='') except Exception, e: return jsonify(ok=False, msg=e.message) @app.route('/delete_user', methods=['POST']) def delete_user(): try: aaa.delete_user(post_get('username')) return jsonify(ok=True, msg='') except Exception, e: print repr(e) return jsonify(ok=False, msg=e.message) @app.route('/create_role', methods=['POST']) def create_role(): try: aaa.create_role(post_get('role'), post_get('level')) return jsonify(ok=True, msg='') except Exception, e: return jsonify(ok=False, msg=e.message) @app.route('/delete_role', methods=['POST']) def delete_role(): try: aaa.delete_role(post_get('role')) return jsonify(ok=True, msg='') except Exception, e: return jsonify(ok=False, msg=e.message) # Static pages @app.route('/login') def login_form(): """Serve login form""" return flask.render_template('login_form.html') @app.route('/sorry_page') def sorry_page(): """Serve sorry page""" return '

Sorry, you are not authorized to perform this action

' # # Web application main # # app.secret_key = os.urandom(24) #FIXME: why def main(): # Start the Bottle webapp #bottle.debug(True) app.secret_key = os.urandom(24) app.run(debug=True, use_reloader=True) if __name__ == "__main__": main() bottle-cork-0.12.0/tests/test_webtest_parallel.py0000644000175000017500000002004112314374426021756 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Functional test using Json backend # # Requires WebTest http://webtest.pythonpaste.org/ # # Run as: nosetests functional_test.py # from nose import SkipTest from time import time from datetime import datetime from webtest import TestApp import glob import json import os import shutil import testutils from cork import Cork REDIR = 302 class Test(testutils.WebFunctional): def __init__(self): self._tmpdir = None self._tmproot = None self._app = None self._starting_dir = os.getcwd() def populate_conf_directory(self): """Populate a directory with valid configuration files, to be run just once The files are not modified by each test """ self._tmpdir = os.path.join(self._tmproot, "cork_functional_test_source") # only do this once, as advertised if os.path.exists(self._tmpdir): return os.mkdir(self._tmpdir) os.mkdir(self._tmpdir + "/example_conf") cork = Cork(os.path.join(self._tmpdir, "example_conf"), initialize=True) cork._store.roles['admin'] = 100 cork._store.roles['editor'] = 60 cork._store.roles['user'] = 50 cork._store.save_roles() tstamp = str(datetime.utcnow()) username = password = 'admin' cork._store.users[username] = { 'role': 'admin', 'hash': cork._hash(username, password), 'email_addr': username + '@localhost.local', 'desc': username + ' test user', 'creation_date': tstamp } username = password = '' cork._store.users[username] = { 'role': 'user', 'hash': cork._hash(username, password), 'email_addr': username + '@localhost.local', 'desc': username + ' test user', 'creation_date': tstamp } cork._store.save_users() def remove_temp_dir(self): p = os.path.join(self._tmproot, 'cork_functional_test_wd') for f in glob.glob('%s*' % p): #shutil.rmtree(f) pass @classmethod def setUpClass(cls): print("Setup class") def setup(self): # create test dir and populate it using the example files # save the directory where the unit testing has been run if self._starting_dir is None: self._starting_dir = os.getcwd() # create json files to be used by Cork self._tmproot = testutils.pick_temp_directory() assert self._tmproot is not None # purge the temporary test directory self.remove_temp_dir() self.populate_temp_dir() self.create_app_instance() self._app.reset() print("Reset done") print("Setup completed") def populate_temp_dir(self): """populate the temporary test dir""" assert self._tmproot is not None assert self._tmpdir is None tstamp = str(time())[5:] self._tmpdir = os.path.join(self._tmproot, "cork_functional_test_wd_%s" % tstamp) try: os.mkdir(self._tmpdir) except OSError: # The directory is already there, purge it print("Deleting %s" % self._tmpdir) shutil.rmtree(self._tmpdir) os.mkdir(self._tmpdir) #p = os.path.join(self._tmproot, 'cork_functional_test_wd') #for f in glob.glob('%s*' % p): # shutil.rmtree(f) # copy the needed files shutil.copytree( os.path.join(self._starting_dir, 'tests/example_conf'), os.path.join(self._tmpdir, 'example_conf') ) shutil.copytree( os.path.join(self._starting_dir, 'tests/views'), os.path.join(self._tmpdir, 'views') ) # change to the temporary test directory # cork relies on this being the current directory os.chdir(self._tmpdir) print("Test directory set up") def create_app_instance(self): """create TestApp instance""" assert self._app is None import simple_webapp self._bottle_app = simple_webapp.app self._app = TestApp(self._bottle_app) print("Test App created") def teardown(self): print("Doing teardown") try: self._app.post('/logout') except: pass # drop the cookie self._app.reset() assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" # drop the cookie self._app.reset() #assert self._app.get('/admin').status != '200 OK' os.chdir(self._starting_dir) #if self._tmproot is not None: # testutils.purge_temp_directory(self._tmproot) self._app.app.options['timeout'] = self._default_timeout self._app = None self._tmproot = None self._tmpdir = None print("Teardown done") def setup(self): # create test dir and populate it using the example files # save the directory where the unit testing has been run if self._starting_dir is None: self._starting_dir = os.getcwd() # create json files to be used by Cork self._tmproot = testutils.pick_temp_directory() assert self._tmproot is not None # purge the temporary test directory self.remove_temp_dir() self.populate_temp_dir() self.create_app_instance() self._app.reset() print("Reset done") self._default_timeout = self._app.app.options['timeout'] print("Setup completed") def assert_200(self, path, match): """Assert that a page returns 200""" p = self._app.get(path) assert p.status_int == 200, "Status: %d, Location: %s" % \ (p.status_int, p.location) if match is not None: assert match in p.body, "'%s' not found in body: '%s'" % (match, p.body) return p def assert_redirect(self, page, redir_page, post=None): """Assert that a page redirects to another one""" # perform GET or POST if post is None: p = self._app.get(page, status=302) else: assert isinstance(post, dict) p = self._app.post(page, post, status=302) dest = p.location.split(':80/')[-1] dest = "/%s" % dest assert dest == redir_page, "%s redirects to %s instead of %s" % \ (page, dest, redir_page) return p def login_as_admin(self): """perform log in""" assert self._app is not None assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" self.assert_200('/login', 'Please insert your credentials') assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" self.assert_redirect('/admin', '/sorry_page') self.assert_200('/user_is_anonymous', 'True') assert 'beaker.session.id' not in self._app.cookies, "Unexpected cookie found" post = {'username': 'admin', 'password': 'admin'} self.assert_redirect('/login', '/', post=post) assert 'beaker.session.id' in self._app.cookies, "Cookie not found" import bottle session = bottle.request.environ.get('beaker.session') print("Session from func. test", repr(session)) self.assert_200('/login', 'Please insert your credentials') p = self._app.get('/admin') assert 'Welcome' in p.body, repr(p) p = self._app.get('/my_role', status=200) assert p.status == '200 OK' assert p.body == 'admin', "Sta" print("Login performed") def test_functional_expiration(self): self.login_as_admin() r = self._app.get('/admin') assert r.status == '200 OK', repr(r) # change the cookie expiration in order to expire it self._app.app.options['timeout'] = 0 assert self._app.get('/admin').status_int == REDIR, "The cookie should have expired" bottle-cork-0.12.0/tests/multiparam.py0000644000175000017500000000064512453054133017543 0ustar fedefede00000000000000 # content of conftest.py import pytest class A(object): pass @pytest.fixture def base(): print 'INST A' return A() @pytest.fixture(params=["merlinux.eu", "mail.python.org"]) def smtp(request, base): print request.param return base @pytest.fixture(params=['user', 'admin', None]) def auth_status(request, base): return base def test_nyan(smtp, auth_status): assert smtp == auth_status bottle-cork-0.12.0/cork/0000755000175000017500000000000012617467123014616 5ustar fedefede00000000000000bottle-cork-0.12.0/cork/base_backend.py0000644000175000017500000000136012512453671017546 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt """ .. module:: backend.py :synopsis: Base Backend. """ class BackendIOException(Exception): """Generic Backend I/O Exception""" pass def ni(*args, **kwargs): raise NotImplementedError class Backend(object): """Base Backend class - to be subclassed by real backends.""" save_users = ni save_roles = ni save_pending_registrations = ni class Table(object): """Base Table class - to be subclassed by real backends.""" __len__ = ni __contains__ = ni __setitem__ = ni __getitem__ = ni __iter__ = ni iteritems = ni bottle-cork-0.12.0/cork/mongodb_backend.py0000644000175000017500000001244012520166131020251 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt """ .. module:: mongodb_backend :synopsis: MongoDB storage backend. """ from logging import getLogger log = getLogger(__name__) from .base_backend import Backend, Table try: import pymongo is_pymongo_2 = (pymongo.version_tuple[0] == 2) except ImportError: # pragma: no cover pass class MongoTable(Table): """Abstract MongoDB Table. Allow dictionary-like access. """ def __init__(self, name, key_name, collection): self._name = name self._key_name = key_name self._coll = collection def create_index(self): """Create collection index.""" self._coll.create_index( self._key_name, drop_dups=True, unique=True, ) def __len__(self): return self._coll.count() def __contains__(self, value): r = self._coll.find_one({self._key_name: value}) return r is not None def __iter__(self): """Iter on dictionary keys""" if is_pymongo_2: r = self._coll.find(fields=[self._key_name,]) else: r = self._coll.find(projection=[self._key_name,]) return (i[self._key_name] for i in r) def iteritems(self): """Iter on dictionary items. :returns: generator of (key, value) tuples """ r = self._coll.find() for i in r: d = i.copy() d.pop(self._key_name) d.pop('_id') yield (i[self._key_name], d) def pop(self, key_val): """Remove a dictionary item""" r = self[key_val] self._coll.remove({self._key_name: key_val}, w=1) return r class MongoSingleValueTable(MongoTable): """MongoDB table accessible as a simple key -> value dictionary. Used to store roles. """ # Values are stored in a MongoDB "column" named "val" def __init__(self, *args, **kw): super(MongoSingleValueTable, self).__init__(*args, **kw) def __setitem__(self, key_val, data): assert not isinstance(data, dict) spec = {self._key_name: key_val} data = {self._key_name: key_val, 'val': data} if is_pymongo_2: self._coll.update(spec, {'$set': data}, upsert=True, w=1) else: self._coll.update_one(spec, {'$set': data}, upsert=True) def __getitem__(self, key_val): r = self._coll.find_one({self._key_name: key_val}) if r is None: raise KeyError(key_val) return r['val'] class MongoMutableDict(dict): """Represent an item from a Table. Acts as a dictionary. """ def __init__(self, parent, root_key, d): """Create a MongoMutableDict instance. :param parent: Table instance :type parent: :class:`MongoTable` """ super(MongoMutableDict, self).__init__(d) self._parent = parent self._root_key = root_key def __setitem__(self, k, v): super(MongoMutableDict, self).__setitem__(k, v) spec = {self._parent._key_name: self._root_key} if is_pymongo_2: r = self._parent._coll.update(spec, {'$set': {k: v}}, upsert=True) else: r = self._parent._coll.update_one(spec, {'$set': {k: v}}, upsert=True) class MongoMultiValueTable(MongoTable): """MongoDB table accessible as a dictionary. """ def __init__(self, *args, **kw): super(MongoMultiValueTable, self).__init__(*args, **kw) def __setitem__(self, key_val, data): assert isinstance(data, dict) key_name = self._key_name if key_name in data: assert data[key_name] == key_val else: data[key_name] = key_val spec = {key_name: key_val} if u'_id' in data: del(data[u'_id']) if is_pymongo_2: self._coll.update(spec, {'$set': data}, upsert=True, w=1) else: self._coll.update_one(spec, {'$set': data}, upsert=True) def __getitem__(self, key_val): r = self._coll.find_one({self._key_name: key_val}) if r is None: raise KeyError(key_val) return MongoMutableDict(self, key_val, r) class MongoDBBackend(Backend): def __init__(self, db_name='cork', hostname='localhost', port=27017, initialize=False, username=None, password=None): """Initialize MongoDB Backend""" connection = pymongo.MongoClient(host=hostname, port=port) db = connection[db_name] if username and password: db.authenticate(username, password) self.users = MongoMultiValueTable('users', 'login', db.users) self.pending_registrations = MongoMultiValueTable( 'pending_registrations', 'pending_registration', db.pending_registrations ) self.roles = MongoSingleValueTable('roles', 'role', db.roles) if initialize: self._initialize_storage() def _initialize_storage(self): """Create MongoDB indexes.""" for c in (self.users, self.roles, self.pending_registrations): c.create_index() def save_users(self): pass def save_roles(self): pass def save_pending_registrations(self): pass bottle-cork-0.12.0/cork/json_backend.py0000644000175000017500000001053612517131104017577 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt """ .. module:: json_backend :synopsis: JSON file-based storage backend. """ from logging import getLogger import os import shutil import sys try: import json except ImportError: # pragma: no cover import simplejson as json from .base_backend import BackendIOException is_py3 = (sys.version_info.major == 3) log = getLogger(__name__) try: dict.iteritems py23dict = dict except AttributeError: class py23dict(dict): iteritems = dict.items class BytesEncoder(json.JSONEncoder): def default(self, obj): if is_py3 and isinstance(obj, bytes): return obj.decode() return json.JSONEncoder.default(self, obj) class JsonBackend(object): """JSON file-based storage backend.""" def __init__(self, directory, users_fname='users', roles_fname='roles', pending_reg_fname='register', initialize=False): """Data storage class. Handles JSON files :param users_fname: users file name (without .json) :type users_fname: str. :param roles_fname: roles file name (without .json) :type roles_fname: str. :param pending_reg_fname: pending registrations file name (without .json) :type pending_reg_fname: str. :param initialize: create empty JSON files (defaults to False) :type initialize: bool. """ assert directory, "Directory name must be valid" self._directory = directory self.users = py23dict() self._users_fname = users_fname self.roles = py23dict() self._roles_fname = roles_fname self._mtimes = py23dict() self._pending_reg_fname = pending_reg_fname self.pending_registrations = py23dict() if initialize: self._initialize_storage() self._refresh() # load users and roles def _initialize_storage(self): """Create empty JSON files""" self._savejson(self._users_fname, {}) self._savejson(self._roles_fname, {}) self._savejson(self._pending_reg_fname, {}) def _refresh(self): """Load users and roles from JSON files, if needed""" self._loadjson(self._users_fname, self.users) self._loadjson(self._roles_fname, self.roles) self._loadjson(self._pending_reg_fname, self.pending_registrations) def _loadjson(self, fname, dest): """Load JSON file located under self._directory, if needed :param fname: short file name (without path and .json) :type fname: str. :param dest: destination :type dest: dict """ try: fname = "%s/%s.json" % (self._directory, fname) mtime = os.stat(fname).st_mtime if self._mtimes.get(fname, 0) == mtime: # no need to reload the file: the mtime has not been changed return with open(fname) as f: json_data = f.read() except Exception as e: raise BackendIOException("Unable to read json file %s: %s" % (fname, e)) try: json_obj = json.loads(json_data) dest.clear() dest.update(json_obj) self._mtimes[fname] = os.stat(fname).st_mtime except Exception as e: raise BackendIOException("Unable to parse JSON data from %s: %s" \ % (fname, e)) def _savejson(self, fname, obj): """Save obj in JSON format in a file in self._directory""" fname = "%s/%s.json" % (self._directory, fname) try: with open("%s.tmp" % fname, 'w') as f: json.dump(obj, f, cls=BytesEncoder) f.flush() shutil.move("%s.tmp" % fname, fname) except Exception as e: raise BackendIOException("Unable to save JSON file %s: %s" \ % (fname, e)) def save_users(self): """Save users in a JSON file""" self._savejson(self._users_fname, self.users) def save_roles(self): """Save roles in a JSON file""" self._savejson(self._roles_fname, self.roles) def save_pending_registrations(self): """Save pending registrations in a JSON file""" self._savejson(self._pending_reg_fname, self.pending_registrations) bottle-cork-0.12.0/cork/cork.py0000644000175000017500000010162612617465100016125 0ustar fedefede00000000000000#!/usr/bin/env python # # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # # This package is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 3 of the License, or (at your option) any later version. # # This package is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # from base64 import b64encode, b64decode from datetime import datetime, timedelta from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from logging import getLogger from smtplib import SMTP, SMTP_SSL from threading import Thread from time import time import bottle import hashlib import os import re import sys import uuid if sys.version_info[0:3] < (2, 7, 8): print("Python >= 2.7.8 is required") sys.exit(1) try: import scrypt scrypt_available = True except ImportError: # pragma: no cover scrypt_available = False try: basestring except NameError: basestring = str from .backends import JsonBackend is_py3 = (sys.version_info.major == 3) log = getLogger(__name__) class AAAException(Exception): """Generic Authentication/Authorization Exception""" pass class AuthException(AAAException): """Authentication Exception: incorrect username/password pair""" pass class BaseCork(object): """Abstract class""" def __init__(self, directory=None, backend=None, email_sender=None, initialize=False, session_domain=None, smtp_server=None, smtp_url='localhost', session_key_name=None): """Auth/Authorization/Accounting class :param directory: configuration directory :type directory: str. :param users_fname: users filename (without .json), defaults to 'users' :type users_fname: str. :param roles_fname: roles filename (without .json), defaults to 'roles' :type roles_fname: str. """ if smtp_server: smtp_url = smtp_server self.mailer = Mailer(email_sender, smtp_url) self.password_reset_timeout = 3600 * 24 self.session_domain = session_domain self.session_key_name = session_key_name or 'beaker.session' self.preferred_hashing_algorithm = 'PBKDF2' # Setup JsonBackend by default for backward compatibility. if backend is None: self._store = JsonBackend( directory, users_fname='users', roles_fname='roles', pending_reg_fname='register', initialize=initialize ) else: self._store = backend def login(self, username, password, success_redirect=None, fail_redirect=None): """Check login credentials for an existing user. Optionally redirect the user to another page (typically /login) :param username: username :type username: str or unicode. :param password: cleartext password :type password: str.or unicode :param success_redirect: redirect authorized users (optional) :type success_redirect: str. :param fail_redirect: redirect unauthorized users (optional) :type fail_redirect: str. :returns: True for successful logins, else False """ # assert isinstance(username, type(u'')), "username must be a string" # assert isinstance(password, type(u'')), "password must be a string" if username in self._store.users: salted_hash = self._store.users[username]['hash'] if hasattr(salted_hash, 'encode'): salted_hash = salted_hash.encode('ascii') authenticated = self._verify_password( username, password, salted_hash, ) if authenticated: # Setup session data self._setup_cookie(username) self._store.users[username]['last_login'] = str( datetime.utcnow()) self._store.save_users() if success_redirect: self._redirect(success_redirect) return True if fail_redirect: self._redirect(fail_redirect) return False def logout(self, success_redirect='/login', fail_redirect='/login'): """Log the user out, remove cookie :param success_redirect: redirect the user after logging out :type success_redirect: str. :param fail_redirect: redirect the user if it is not logged in :type fail_redirect: str. """ try: session = self._beaker_session session.delete() except Exception as e: log.debug("Exception %s while logging out." % repr(e)) self._redirect(fail_redirect) self._redirect(success_redirect) def require(self, username=None, role=None, fixed_role=False, fail_redirect=None): """Ensure the user is logged in has the required role (or higher). Optionally redirect the user to another page (typically /login) If both `username` and `role` are specified, both conditions need to be satisfied. If none is specified, any authenticated user will be authorized. By default, any role with higher level than `role` will be authorized; set fixed_role=True to prevent this. :param username: username (optional) :type username: str. :param role: role :type role: str. :param fixed_role: require user role to match `role` strictly :type fixed_role: bool. :param redirect: redirect unauthorized users (optional) :type redirect: str. """ # Parameter validation if username is not None: if username not in self._store.users: raise AAAException("Nonexistent user") if fixed_role and role is None: raise AAAException( """A role must be specified if fixed_role has been set""") if role is not None and role not in self._store.roles: raise AAAException("Role not found") # Authentication try: cu = self.current_user except AAAException: if fail_redirect is None: raise AuthException("Unauthenticated user") else: self._redirect(fail_redirect) # Authorization if cu.role not in self._store.roles: raise AAAException("Role not found for the current user") if username is not None: # A specific user is required if username == self.current_user.username: return if fail_redirect is None: raise AuthException("Unauthorized access: incorrect" " username") self._redirect(fail_redirect) if fixed_role: # A specific role is required if role == self.current_user.role: return if fail_redirect is None: raise AuthException("Unauthorized access: incorrect role") self._redirect(fail_redirect) if role is not None: # Any role with higher level is allowed current_lvl = self._store.roles[self.current_user.role] threshold_lvl = self._store.roles[role] if current_lvl >= threshold_lvl: return if fail_redirect is None: raise AuthException("Unauthorized access: ") self._redirect(fail_redirect) return # success def create_role(self, role, level): """Create a new role. :param role: role name :type role: str. :param level: role level (0=lowest, 100=admin) :type level: int. :raises: AuthException on errors """ if self.current_user.level < 100: raise AuthException("The current user is not authorized to ") if role in self._store.roles: raise AAAException("The role is already existing") try: int(level) except ValueError: raise AAAException("The level must be numeric.") self._store.roles[role] = level self._store.save_roles() def delete_role(self, role): """Deleta a role. :param role: role name :type role: str. :raises: AuthException on errors """ if self.current_user.level < 100: raise AuthException("The current user is not authorized to ") if role not in self._store.roles: raise AAAException("Nonexistent role.") self._store.roles.pop(role) self._store.save_roles() def list_roles(self): """List roles. :returns: (role, role_level) generator (sorted by role) """ for role in sorted(self._store.roles): yield (role, self._store.roles[role]) def create_user(self, username, role, password, email_addr=None, description=None): """Create a new user account. This method is available to users with level>=100 :param username: username :type username: str. :param role: role :type role: str. :param password: cleartext password :type password: str. :param email_addr: email address (optional) :type email_addr: str. :param description: description (free form) :type description: str. :raises: AuthException on errors """ assert username, "Username must be provided." if self.current_user.level < 100: raise AuthException("The current user is not authorized" " to create users.") if username in self._store.users: raise AAAException("User is already existing.") if role not in self._store.roles: raise AAAException("Nonexistent user role.") tstamp = str(datetime.utcnow()) h = self._hash(username, password) h = h.decode('ascii') self._store.users[username] = { 'role': role, 'hash': h, 'email_addr': email_addr, 'desc': description, 'creation_date': tstamp, 'last_login': tstamp } self._store.save_users() def delete_user(self, username): """Delete a user account. This method is available to users with level>=100 :param username: username :type username: str. :raises: Exceptions on errors """ if self.current_user.level < 100: raise AuthException("The current user is not authorized to ") if username not in self._store.users: raise AAAException("Nonexistent user.") self.user(username).delete() def list_users(self): """List users. :return: (username, role, email_addr, description) generator (sorted by username) """ for un in sorted(self._store.users): d = self._store.users[un] yield (un, d['role'], d['email_addr'], d['desc']) @property def current_user(self): """Current autenticated user :returns: User() instance, if authenticated :raises: AuthException otherwise """ session = self._beaker_session username = session.get('username', None) if username is None: raise AuthException("Unauthenticated user") if username is not None and username in self._store.users: return User(username, self, session=session) raise AuthException("Unknown user: %s" % username) @property def user_is_anonymous(self): """Check if the current user is anonymous. :returns: True if the user is anonymous, False otherwise :raises: AuthException if the session username is unknown """ try: username = self._beaker_session['username'] except KeyError: return True if username not in self._store.users: raise AuthException("Unknown user: %s" % username) return False def user(self, username): """Existing user :returns: User() instance if the user exist, None otherwise """ if username is not None and username in self._store.users: return User(username, self) return None def register(self, username, password, email_addr, role='user', max_level=50, subject="Signup confirmation", email_template='views/registration_email.tpl', description=None, **kwargs): """Register a new user account. An email with a registration validation is sent to the user. WARNING: this method is available to unauthenticated users :param username: username :type username: str. :param password: cleartext password :type password: str. :param role: role (optional), defaults to 'user' :type role: str. :param max_level: maximum role level (optional), defaults to 50 :type max_level: int. :param email_addr: email address :type email_addr: str. :param subject: email subject :type subject: str. :param email_template: email template filename :type email_template: str. :param description: description (free form) :type description: str. :raises: AssertError or AAAException on errors """ assert username, "Username must be provided." assert password, "A password must be provided." assert email_addr, "An email address must be provided." if username in self._store.users: raise AAAException("User is already existing.") if role not in self._store.roles: raise AAAException("Nonexistent role") if self._store.roles[role] > max_level: raise AAAException("Unauthorized role") registration_code = uuid.uuid4().hex creation_date = str(datetime.utcnow()) # send registration email email_text = bottle.template( email_template, username=username, email_addr=email_addr, role=role, creation_date=creation_date, registration_code=registration_code, **kwargs ) self.mailer.send_email(email_addr, subject, email_text) # store pending registration h = self._hash(username, password) h = h.decode('ascii') self._store.pending_registrations[registration_code] = { 'username': username, 'role': role, 'hash': h, 'email_addr': email_addr, 'desc': description, 'creation_date': creation_date, } self._store.save_pending_registrations() def validate_registration(self, registration_code): """Validate pending account registration, create a new account if successful. :param registration_code: registration code :type registration_code: str. """ try: data = self._store.pending_registrations.pop(registration_code) except KeyError: raise AuthException("Invalid registration code.") username = data['username'] if username in self._store.users: raise AAAException("User is already existing.") # the user data is moved from pending_registrations to _users self._store.users[username] = { 'role': data['role'], 'hash': data['hash'], 'email_addr': data['email_addr'], 'desc': data['desc'], 'creation_date': data['creation_date'], 'last_login': str(datetime.utcnow()) } self._store.save_users() def send_password_reset_email(self, username=None, email_addr=None, subject="Password reset confirmation", email_template='views/password_reset_email', **kwargs): """Email the user with a link to reset his/her password If only one parameter is passed, fetch the other from the users database. If both are passed they will be matched against the users database as a security check. :param username: username :type username: str. :param email_addr: email address :type email_addr: str. :param subject: email subject :type subject: str. :param email_template: email template filename :type email_template: str. :raises: AAAException on missing username or email_addr, AuthException on incorrect username/email_addr pair """ if not username: if not email_addr: raise AAAException("At least `username` or `email_addr` must" " be specified.") # only email_addr is specified: fetch the username for k, v in self._store.users.iteritems(): if v['email_addr'] == email_addr: username = k break else: raise AAAException("Email address not found.") else: # username is provided if username not in self._store.users: raise AAAException("Nonexistent user.") if not email_addr: email_addr = self._store.users[username].get('email_addr', None) if not email_addr: raise AAAException("Email address not available.") else: # both username and email_addr are provided: check them stored_email_addr = self._store.users[username]['email_addr'] if email_addr != stored_email_addr: raise AuthException( "Username/email address pair not found.") # generate a reset_code token reset_code = self._reset_code(username, email_addr) # send reset email email_text = bottle.template( email_template, username=username, email_addr=email_addr, reset_code=reset_code, **kwargs ) self.mailer.send_email(email_addr, subject, email_text) def reset_password(self, reset_code, password): """Validate reset_code and update the account password The username is extracted from the reset_code token :param reset_code: reset token :type reset_code: str. :param password: new password :type password: str. :raises: AuthException for invalid reset tokens, AAAException """ try: reset_code = b64decode(reset_code).decode() username, email_addr, tstamp, h = reset_code.split(':', 3) tstamp = int(tstamp) assert isinstance(username, type(u'')) assert isinstance(email_addr, type(u'')) if not isinstance(h, type(b'')): h = h.encode('utf-8') except (TypeError, ValueError): raise AuthException("Invalid reset code.") if time() - tstamp > self.password_reset_timeout: raise AuthException("Expired reset code.") assert isinstance(h, type(b'')) if not self._verify_password(username, email_addr, h): raise AuthException("Invalid reset code.") user = self.user(username) if user is None: raise AAAException("Nonexistent user.") user.update(pwd=password) def make_auth_decorator(self, username=None, role=None, fixed_role=False, fail_redirect='/login'): ''' Create a decorator to be used for authentication and authorization :param username: A resource can be protected for a specific user :param role: Minimum role level required for authorization :param fixed_role: Only this role gets authorized :param fail_redirect: The URL to redirect to if a login is required. ''' session_manager = self def auth_require(username=username, role=role, fixed_role=fixed_role, fail_redirect=fail_redirect): def decorator(func): import functools @functools.wraps(func) def wrapper(*a, **ka): session_manager.require( username=username, role=role, fixed_role=fixed_role, fail_redirect=fail_redirect) return func(*a, **ka) return wrapper return decorator return(auth_require) ## Private methods def _setup_cookie(self, username): """Setup cookie for a user that just logged in""" session = self._beaker_session session['username'] = username if self.session_domain is not None: session.domain = self.session_domain self._save_session() def _hash(self, username, pwd, salt=None, algo=None): """Hash username and password, generating salt value if required """ if algo is None: algo = self.preferred_hashing_algorithm if algo == 'PBKDF2': return self._hash_pbkdf2(username, pwd, salt=salt) if algo == 'scrypt': return self._hash_scrypt(username, pwd, salt=salt) raise RuntimeError("Unknown hashing algorithm requested: %s" % algo) @staticmethod def _hash_scrypt(username, pwd, salt=None): """Hash username and password, generating salt value if required Use scrypt. :returns: base-64 encoded str. """ if not scrypt_available: raise Exception("scrypt.hash required." " Please install the scrypt library.") if salt is None: salt = os.urandom(32) assert len(salt) == 32, "Incorrect salt length" cleartext = "%s\0%s" % (username, pwd) h = scrypt.hash(cleartext, salt) # 's' for scrypt hashed = b's' + salt + h return b64encode(hashed) @staticmethod def _hash_pbkdf2(username, pwd, salt=None): """Hash username and password, generating salt value if required Use PBKDF2 from Beaker :returns: base-64 encoded str. """ if salt is None: salt = os.urandom(32) assert isinstance(salt, bytes) assert len(salt) == 32, "Incorrect salt length" username = username.encode('utf-8') assert isinstance(username, bytes) pwd = pwd.encode('utf-8') assert isinstance(pwd, bytes) cleartext = username + b'\0' + pwd h = hashlib.pbkdf2_hmac('sha1', cleartext, salt, 10, dklen=32) # 'p' for PBKDF2 hashed = b'p' + salt + h return b64encode(hashed) def _verify_password(self, username, pwd, salted_hash): """Verity username/password pair against a salted hash :returns: bool """ assert isinstance(salted_hash, type(b'')) decoded = b64decode(salted_hash) hash_type = decoded[0] if isinstance(hash_type, int): hash_type = chr(hash_type) salt = decoded[1:33] if hash_type == 'p': # PBKDF2 h = self._hash_pbkdf2(username, pwd, salt) return salted_hash == h if hash_type == 's': # scrypt h = self._hash_scrypt(username, pwd, salt) return salted_hash == h raise RuntimeError("Unknown hashing algorithm in hash: %r" % decoded) def _purge_expired_registrations(self, exp_time=96): """Purge expired registration requests. :param exp_time: expiration time (hours) :type exp_time: float. """ pending = self._store.pending_registrations.items() if is_py3: pending = list(pending) for uuid_code, data in pending: creation = datetime.strptime(data['creation_date'], "%Y-%m-%d %H:%M:%S.%f") now = datetime.utcnow() maxdelta = timedelta(hours=exp_time) if now - creation > maxdelta: self._store.pending_registrations.pop(uuid_code) def _reset_code(self, username, email_addr): """generate a reset_code token :param username: username :type username: str. :param email_addr: email address :type email_addr: str. :returns: Base-64 encoded token """ h = self._hash(username, email_addr) t = "%d" % time() t = t.encode('utf-8') reset_code = b':'.join((username.encode('utf-8'), email_addr.encode('utf-8'), t, h)) return b64encode(reset_code) class User(object): def __init__(self, username, cork_obj, session=None): """Represent an authenticated user, exposing useful attributes: username, role, level, description, email_addr, session_creation_time, session_accessed_time, session_id. The session-related attributes are available for the current user only. :param username: username :type username: str. :param cork_obj: instance of :class:`Cork` """ self._cork = cork_obj assert username in self._cork._store.users, "Unknown user" self.username = username user_data = self._cork._store.users[username] self.role = user_data['role'] self.description = user_data['desc'] self.email_addr = user_data['email_addr'] self.level = self._cork._store.roles[self.role] if session is not None: try: self.session_creation_time = session['_creation_time'] self.session_accessed_time = session['_accessed_time'] self.session_id = session['_id'] except: pass def update(self, role=None, pwd=None, email_addr=None): """Update an user account data :param role: change user role, if specified :type role: str. :param pwd: change user password, if specified :type pwd: str. :param email_addr: change user email address, if specified :type email_addr: str. :raises: AAAException on nonexistent user or role. """ username = self.username if username not in self._cork._store.users: raise AAAException("User does not exist.") if role is not None: if role not in self._cork._store.roles: raise AAAException("Nonexistent role.") self._cork._store.users[username]['role'] = role if pwd is not None: self._cork._store.users[username]['hash'] = self._cork._hash( username, pwd).decode() if email_addr is not None: self._cork._store.users[username]['email_addr'] = email_addr self._cork._store.save_users() def delete(self): """Delete user account :raises: AAAException on nonexistent user. """ try: self._cork._store.users.pop(self.username) except KeyError: raise AAAException("Nonexistent user.") self._cork._store.save_users() class Redirect(Exception): pass def raise_redirect(path): raise Redirect(path) class Cork(BaseCork): @staticmethod def _redirect(location): bottle.redirect(location) @property def _beaker_session(self): """Get session""" return bottle.request.environ.get(self.session_key_name) def _save_session(self): self._beaker_session.save() class FlaskCork(BaseCork): @staticmethod def _redirect(location): raise_redirect(location) @property def _beaker_session(self): """Get session""" import flask return flask.session def _save_session(self): pass class Mailer(object): def __init__(self, sender, smtp_url, join_timeout=5, use_threads=True): """Send emails asyncronously :param sender: Sender email address :type sender: str. :param smtp_server: SMTP server :type smtp_server: str. """ self.sender = sender self.join_timeout = join_timeout self.use_threads = use_threads self._threads = [] self._conf = self._parse_smtp_url(smtp_url) def _parse_smtp_url(self, url): """Parse SMTP URL""" match = re.match(r""" ( # Optional protocol (?Psmtp|starttls|ssl) # Protocol name :// )? ( # Optional user:pass@ (?P[^:]*) # Match every char except ':' (: (?P.*) )? @ # Optional :pass )? (?P # Required FQDN on IP address ()| # Empty string ( # FQDN [a-zA-Z_\-] # First character cannot be a number [a-zA-Z0-9_\-\.]{,254} ) |( # IPv4 ([0-9]{1,3}\.){3} [0-9]{1,3} ) |( # IPv6 \[ # Square brackets ([0-9a-f]{,4}:){1,8} [0-9a-f]{,4} \] ) ) ( # Optional :port : (?P[0-9]{,5}) # Up to 5-digits port )? [/]? $ """, url, re.VERBOSE) if not match: raise RuntimeError("SMTP URL seems incorrect") d = match.groupdict() if d['proto'] is None: d['proto'] = 'smtp' if d['port'] is None: d['port'] = 25 else: d['port'] = int(d['port']) if not 0 < d['port'] < 65536: raise RuntimeError("Incorrect SMTP port") return d def send_email(self, email_addr, subject, email_text): """Send an email :param email_addr: email address :type email_addr: str. :param subject: subject :type subject: str. :param email_text: email text :type email_text: str. :raises: AAAException if smtp_server and/or sender are not set """ if not (self._conf['fqdn'] and self.sender): raise AAAException("SMTP server or sender not set") msg = MIMEMultipart('alternative') msg['Subject'] = subject msg['From'] = self.sender msg['To'] = email_addr if isinstance(email_text, bytes): email_text = email_text.encode('utf-8') part = MIMEText(email_text, 'html') msg.attach(part) msg = msg.as_string() log.debug("Sending email using %s" % self._conf['fqdn']) if self.use_threads: thread = Thread(target=self._send, args=(email_addr, msg)) thread.start() self._threads.append(thread) else: self._send(email_addr, msg) def _send(self, email_addr, msg): """Deliver an email using SMTP :param email_addr: recipient :type email_addr: str. :param msg: email text :type msg: str. """ proto = self._conf['proto'] assert proto in ('smtp', 'starttls', 'ssl'), \ "Incorrect protocol: %s" % proto try: if proto == 'ssl': log.debug("Setting up SSL") session = SMTP_SSL(self._conf['fqdn'], self._conf['port']) else: session = SMTP(self._conf['fqdn'], self._conf['port']) if proto == 'starttls': log.debug('Sending EHLO and STARTTLS') session.ehlo() session.starttls() session.ehlo() if self._conf['user'] is not None: log.debug('Performing login') session.login(self._conf['user'], self._conf['pass']) log.debug('Sending') session.sendmail(self.sender, email_addr, msg) session.quit() log.info('Email sent') except Exception as e: # pragma: no cover log.error("Error sending email: %s" % e, exc_info=True) def join(self): """Flush email queue by waiting the completion of the existing threads :returns: None """ return [t.join(self.join_timeout) for t in self._threads] def __del__(self): """Class destructor: wait for threads to terminate within a timeout""" try: self.join() except TypeError: pass bottle-cork-0.12.0/cork/sqlite_backend.py0000644000175000017500000001666612552271756020162 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt """ .. module:: sqlite_backend :synopsis: SQLite storage backend. """ from . import base_backend from logging import getLogger log = getLogger(__name__) class SqlRowProxy(dict): def __init__(self, table, key, row): li = ((k, v) for (k, ktype), v in zip(table._columns[1:], row[1:])) dict.__init__(self, li) self._table = table self._key = key def __setitem__(self, key, value): dict.__setitem__(self, key, value) self._table[self._key] = self class Table(base_backend.Table): """Provides dictionary-like access to an SQL table.""" def __init__(self, backend, table_name): self._backend = backend self._engine = backend.connection self._table_name = table_name self._column_names = [n for n, t in self._columns] self._key_col_num = 0 self._key_col_name = self._column_names[self._key_col_num] self._key_col = self._column_names[self._key_col_num] def _row_to_value(self, key, row): assert isinstance(row, tuple) row_key = row[self._key_col_num] row_value = SqlRowProxy(self, key, row) return row_key, row_value def __len__(self): query = "SELECT count() FROM %s" % self._table_name ret = self._backend.run_query(query) return ret.fetchone()[0] def __contains__(self, key): #FIXME: count() query = "SELECT * FROM %s WHERE %s='%s'" % \ (self._table_name, self._key_col, key) row = self._backend.fetch_one(query) return row is not None def __setitem__(self, key, value): """Create or update a row""" assert isinstance(value, dict) v, cn = set(value), set(self._column_names[1:]) assert not v - cn, repr(v - cn) assert not cn - v, repr(cn - v) assert set(value) == set(self._column_names[1:]), "%s %s" % \ (repr(set(value)), repr(set(self._column_names[1:]))) col_values = [key] + [value[k] for k in self._column_names[1:]] col_names = ', '.join(self._column_names) question_marks = ', '.join('?' for x in col_values) query = "INSERT OR REPLACE INTO %s (%s) VALUES (%s)" % \ (self._table_name, col_names, question_marks) ret = self._backend.run_query_using_conversion(query, col_values) def __getitem__(self, key): query = "SELECT * FROM %s WHERE %s='%s'" % \ (self._table_name, self._key_col, key) row = self._backend.fetch_one(query) if row is None: raise KeyError(key) return self._row_to_value(key, row)[1] #return dict(zip(self._column_names, row)) def __iter__(self): """Iterate over table index key values""" query = "SELECT %s FROM %s" % (self._key_col, self._table_name) result = self._backend.run_query(query) for row in result: yield row[0] def iteritems(self): """Iterate over table rows""" query = "SELECT * FROM %s" % self._table_name result = self._backend.run_query(query) for row in result: d = dict(zip(self._column_names, row)) d.pop(self._key_col) yield (self._key_col, d) def pop(self, key): d = self.__getitem__(key) query = "DELETE FROM %s WHERE %s='%s'" % \ (self._table_name, self._key_col, key) self._backend.fetch_one(query) #FIXME: check deletion return d def insert(self, d): raise NotImplementedError def empty_table(self): raise NotImplementedError def create_table(self): """Issue table creation""" cc = [] for col_name, col_type in self._columns: if col_type == int: col_type = 'INTEGER' elif col_type == str: col_type = 'TEXT' if col_name == self._key_col: extras = 'PRIMARY KEY ASC' else: extras = '' cc.append("%s %s %s" % (col_name, col_type, extras)) cc = ','.join(cc) query = "CREATE TABLE %s (%s)" % (self._table_name, cc) self._backend.run_query(query) class SingleValueTable(Table): def __init__(self, *args): super(SingleValueTable, self).__init__(*args) self._value_col = self._column_names[1] def __setitem__(self, key, value): """Create or update a row""" assert not isinstance(value, dict) query = "INSERT OR REPLACE INTO %s (%s, %s) VALUES (?, ?)" % \ (self._table_name, self._key_col, self._value_col) col_values = (key, value) ret = self._backend.run_query_using_conversion(query, col_values) def __getitem__(self, key): query = "SELECT %s FROM %s WHERE %s='%s'" % \ (self._value_col, self._table_name, self._key_col, key) row = self._backend.fetch_one(query) if row is None: raise KeyError(key) return row[0] class UsersTable(Table): def __init__(self, *args, **kwargs): self._columns = ( ('username', str), ('role', str), ('hash', str), ('email_addr', str), ('desc', str), ('creation_date', str), ('last_login', str) ) super(UsersTable, self).__init__(*args, **kwargs) class RolesTable(SingleValueTable): def __init__(self, *args, **kwargs): self._columns = ( ('role', str), ('level', int) ) super(RolesTable, self).__init__(*args, **kwargs) class PendingRegistrationsTable(Table): def __init__(self, *args, **kwargs): self._columns = ( ('code', str), ('username', str), ('role', str), ('hash', str), ('email_addr', str), ('desc', str), ('creation_date', str) ) super(PendingRegistrationsTable, self).__init__(*args, **kwargs) class SQLiteBackend(base_backend.Backend): def __init__(self, filename, users_tname='users', roles_tname='roles', pending_reg_tname='register', initialize=False): self._filename = filename self.users = UsersTable(self, users_tname) self.roles = RolesTable(self, roles_tname) self.pending_registrations = PendingRegistrationsTable(self, pending_reg_tname) if initialize: self.users.create_table() self.roles.create_table() self.pending_registrations.create_table() log.debug("Tables created") @property def connection(self): try: return self._connection except AttributeError: import sqlite3 self._connection = sqlite3.connect(self._filename, isolation_level=None) return self._connection def run_query(self, query): return self._connection.execute(query) def run_query_using_conversion(self, query, args): return self._connection.execute(query, args) def fetch_one(self, query): return self._connection.execute(query).fetchone() def _initialize_storage(self, db_name): raise NotImplementedError def _drop_all_tables(self): raise NotImplementedError def save_users(self): pass def save_roles(self): pass def save_pending_registrations(self): pass bottle-cork-0.12.0/cork/sqlalchemy_backend.py0000644000175000017500000001535112617446061021004 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt """ .. module:: sqlalchemy_backend :synopsis: SQLAlchemy storage backend. """ import sys from logging import getLogger from . import base_backend log = getLogger(__name__) is_py3 = (sys.version_info.major == 3) try: from sqlalchemy import create_engine, delete, select, \ Column, ForeignKey, Integer, MetaData, String, Table, Unicode sqlalchemy_available = True except ImportError: # pragma: no cover sqlalchemy_available = False class SqlRowProxy(dict): def __init__(self, sql_dict, key, *args, **kwargs): dict.__init__(self, *args, **kwargs) self.sql_dict = sql_dict self.key = key def __setitem__(self, key, value): dict.__setitem__(self, key, value) if self.sql_dict is not None: self.sql_dict[self.key] = {key: value} class SqlTable(base_backend.Table): """Provides dictionary-like access to an SQL table.""" def __init__(self, engine, table, key_col_name): self._engine = engine self._table = table self._key_col = table.c[key_col_name] def _row_to_value(self, row): row_key = row[self._key_col] row_value = SqlRowProxy(self, row_key, ((k, row[k]) for k in row.keys() if k != self._key_col.name)) return row_key, row_value def __len__(self): query = self._table.count() c = self._engine.execute(query).scalar() return int(c) def __contains__(self, key): query = select([self._key_col], self._key_col == key) row = self._engine.execute(query).fetchone() return row is not None def __setitem__(self, key, value): if key in self: values = value query = self._table.update().where(self._key_col == key) else: values = {self._key_col.name: key} values.update(value) query = self._table.insert() self._engine.execute(query.values(**values)) def __getitem__(self, key): query = select([self._table], self._key_col == key) row = self._engine.execute(query).fetchone() if row is None: raise KeyError(key) return self._row_to_value(row)[1] def __iter__(self): """Iterate over table index key values""" query = select([self._key_col]) result = self._engine.execute(query) for row in result: key = row[0] yield key def iteritems(self): """Iterate over table rows""" query = select([self._table]) result = self._engine.execute(query) for row in result: key = row[0] d = self._row_to_value(row)[1] yield (key, d) def pop(self, key): query = select([self._table], self._key_col == key) row = self._engine.execute(query).fetchone() if row is None: raise KeyError query = delete(self._table, self._key_col == key) self._engine.execute(query) return row def insert(self, d): query = self._table.insert(d) self._engine.execute(query) log.debug("%s inserted" % repr(d)) def empty_table(self): query = self._table.delete() self._engine.execute(query) log.info("Table purged") class SqlSingleValueTable(SqlTable): def __init__(self, engine, table, key_col_name, col_name): SqlTable.__init__(self, engine, table, key_col_name) self._col_name = col_name def _row_to_value(self, row): return row[self._key_col], row[self._col_name] def __setitem__(self, key, value): SqlTable.__setitem__(self, key, {self._col_name: value}) class SqlAlchemyBackend(base_backend.Backend): def __init__(self, db_full_url, users_tname='users', roles_tname='roles', pending_reg_tname='register', initialize=False, **kwargs): if not sqlalchemy_available: raise RuntimeError("The SQLAlchemy library is not available.") self._metadata = MetaData() if initialize: # Create new database if needed. db_url, db_name = db_full_url.rsplit('/', 1) if is_py3 and db_url.startswith('mysql'): print("WARNING: MySQL is not supported under Python3") self._engine = create_engine(db_url, encoding='utf-8', **kwargs) try: self._engine.execute("CREATE DATABASE %s" % db_name) except Exception as e: log.info("Failed DB creation: %s" % e) # SQLite in-memory database URL: "sqlite://:memory:" if db_name != ':memory:' and not db_url.startswith('postgresql'): self._engine.execute("USE %s" % db_name) else: self._engine = create_engine(db_full_url, encoding='utf-8', **kwargs) self._users = Table(users_tname, self._metadata, Column('username', Unicode(128), primary_key=True), Column('role', ForeignKey(roles_tname + '.role')), Column('hash', String(256), nullable=False), Column('email_addr', String(128)), Column('desc', String(128)), Column('creation_date', String(128), nullable=False), Column('last_login', String(128), nullable=False) ) self._roles = Table(roles_tname, self._metadata, Column('role', String(128), primary_key=True), Column('level', Integer, nullable=False) ) self._pending_reg = Table(pending_reg_tname, self._metadata, Column('code', String(128), primary_key=True), Column('username', Unicode(128), nullable=False), Column('role', ForeignKey(roles_tname + '.role')), Column('hash', String(256), nullable=False), Column('email_addr', String(128)), Column('desc', String(128)), Column('creation_date', String(128), nullable=False) ) self.users = SqlTable(self._engine, self._users, 'username') self.roles = SqlSingleValueTable(self._engine, self._roles, 'role', 'level') self.pending_registrations = SqlTable(self._engine, self._pending_reg, 'code') if initialize: self._initialize_storage(db_name) log.debug("Tables created") def _initialize_storage(self, db_name): self._metadata.create_all(self._engine) def _drop_all_tables(self): for table in reversed(self._metadata.sorted_tables): log.info("Dropping table %s" % repr(table.name)) self._engine.execute(table.delete()) def save_users(self): pass def save_roles(self): pass def save_pending_registrations(self): pass bottle-cork-0.12.0/cork/__init__.py0000644000175000017500000000052312617462665016735 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Backends API - used to make backends available for importing # from .cork import Cork, JsonBackend, AAAException, AuthException, Mailer, FlaskCork, Redirect bottle-cork-0.12.0/cork/backends.py0000644000175000017500000000071212517131104016724 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt """ .. module:: backends :synopsis: Backends API - used to make backends available for importing """ from .json_backend import JsonBackend from .mongodb_backend import MongoDBBackend from .sqlalchemy_backend import SqlAlchemyBackend from .sqlite_backend import SQLiteBackend bottle-cork-0.12.0/setup.py0000644000175000017500000000251412617466063015376 0ustar fedefede00000000000000#!/usr/bin/env python from setuptools import setup __version__ = '0.12.0' CLASSIFIERS = map(str.strip, """Development Status :: 4 - Beta Environment :: Web Environment Framework :: Bottle Framework :: Flask Intended Audience :: Developers License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Natural Language :: English Operating System :: POSIX :: Linux Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2.7 Programming Language :: Python :: 3.4 Topic :: Internet :: WWW/HTTP :: WSGI Topic :: Internet :: WWW/HTTP :: WSGI Topic :: Security Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Libraries :: Python Modules """.splitlines()) setup( name="bottle-cork", version=__version__, author="Federico Ceratto", author_email="federico.ceratto@gmail.com", description="Authentication/Authorization library for Bottle", license="LGPLv3+", url="http://cork.firelet.net/", long_description="Cork is a simple Authentication/Authorization library" "for the Bottle and Flask web frameworks.", classifiers=CLASSIFIERS, install_requires=[ 'Bottle', 'pycrypto', ], extras_require={ 'scrypt': ["scrypt>=0.6.1"], }, packages=['cork'], platforms=['Linux'], ) bottle-cork-0.12.0/MANIFEST.in0000644000175000017500000000010212166632355015407 0ustar fedefede00000000000000include tests/*.py include setup.py recursive-include examples * bottle-cork-0.12.0/examples/0000755000175000017500000000000012617467123015476 5ustar fedefede00000000000000bottle-cork-0.12.0/examples/simple_webapp_decorated.py0000755000175000017500000001457712312136477022727 0ustar fedefede00000000000000#!/usr/bin/env python # # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Cork example web application # # The following users are already available: # admin/admin, demo/demo import bottle from beaker.middleware import SessionMiddleware from cork import Cork import logging logging.basicConfig(format='localhost - - [%(asctime)s] %(message)s', level=logging.DEBUG) log = logging.getLogger(__name__) bottle.debug(True) # Use users.json and roles.json in the local example_conf directory aaa = Cork('example_conf', email_sender='federico.ceratto@gmail.com', smtp_url='smtp://smtp.magnet.ie') # alias the authorization decorator with defaults authorize = aaa.make_auth_decorator(fail_redirect="/login", role="user") import datetime app = bottle.app() session_opts = { 'session.cookie_expires': True, 'session.encrypt_key': 'please use a random key and keep it secret!', 'session.httponly': True, 'session.timeout': 3600 * 24, # 1 day 'session.type': 'cookie', 'session.validate_key': True, } app = SessionMiddleware(app, session_opts) # # Bottle methods # # def postd(): return bottle.request.forms def post_get(name, default=''): return bottle.request.POST.get(name, default).strip() @bottle.post('/login') def login(): """Authenticate users""" username = post_get('username') password = post_get('password') aaa.login(username, password, success_redirect='/', fail_redirect='/login') @bottle.route('/user_is_anonymous') def user_is_anonymous(): if aaa.user_is_anonymous: return 'True' return 'False' @bottle.route('/logout') def logout(): aaa.logout(success_redirect='/login') @bottle.post('/register') def register(): """Send out registration email""" aaa.register(post_get('username'), post_get('password'), post_get('email_address')) return 'Please check your mailbox.' @bottle.route('/validate_registration/:registration_code') def validate_registration(registration_code): """Validate registration, create user account""" aaa.validate_registration(registration_code) return 'Thanks. Go to login' @bottle.post('/reset_password') def send_password_reset_email(): """Send out password reset email""" aaa.send_password_reset_email( username=post_get('username'), email_addr=post_get('email_address') ) return 'Please check your mailbox.' @bottle.route('/change_password/:reset_code') @bottle.view('password_change_form') def change_password(reset_code): """Show password change form""" return dict(reset_code=reset_code) @bottle.post('/change_password') def change_password(): """Change password""" aaa.reset_password(post_get('reset_code'), post_get('password')) return 'Thanks. Go to login' @bottle.route('/') @authorize() def index(): """Only authenticated users can see this""" #session = bottle.request.environ.get('beaker.session') #aaa.require(fail_redirect='/login') return 'Welcome! Admin page Logout' # Resources used by tests designed to test decorators specifically @bottle.route('/for_kings_only') @authorize(role="king") def page_for_kings(): """ This resource is used to test a non-existing role. Only kings or higher (e.g. gods) can see this """ return 'Welcome! Admin page Logout' @bottle.route('/page_for_specific_user_admin') @authorize(username="admin") def page_for_username_admin(): """Only a user named 'admin' can see this""" return 'Welcome! Admin page Logout' @bottle.route('/page_for_specific_user_fred_who_doesnt_exist') @authorize(username="fred") def page_for_user_fred(): """Only authenticated users by the name of 'fred' can see this""" return 'Welcome! Admin page Logout' @bottle.route('/page_for_admins') @authorize(role="admin") def page_for_role_admin(): """Only authenticated users (role=user or role=admin) can see this""" return 'Welcome! Admin page Logout' @bottle.route('/restricted_download') @authorize() def restricted_download(): """Only authenticated users can download this file""" #aaa.require(fail_redirect='/login') return bottle.static_file('static_file', root='.') @bottle.route('/my_role') def show_current_user_role(): """Show current user role""" session = bottle.request.environ.get('beaker.session') print "Session from simple_webapp", repr(session) aaa.require(fail_redirect='/login') return aaa.current_user.role # Admin-only pages @bottle.route('/admin') @authorize(role="admin", fail_redirect='/sorry_page') @bottle.view('admin_page') def admin(): """Only admin users can see this""" #aaa.require(role='admin', fail_redirect='/sorry_page') return dict( current_user = aaa.current_user, users = aaa.list_users(), roles = aaa.list_roles() ) @bottle.post('/create_user') def create_user(): try: aaa.create_user(postd().username, postd().role, postd().password) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_user') def delete_user(): try: aaa.delete_user(post_get('username')) return dict(ok=True, msg='') except Exception, e: print repr(e) return dict(ok=False, msg=e.message) @bottle.post('/create_role') def create_role(): try: aaa.create_role(post_get('role'), post_get('level')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_role') def delete_role(): try: aaa.delete_role(post_get('role')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) # Static pages @bottle.route('/login') @bottle.view('login_form') def login_form(): """Serve login form""" return {} @bottle.route('/sorry_page') def sorry_page(): """Serve sorry page""" return '

Sorry, you are not authorized to perform this action

' # # Web application main # # def main(): # Start the Bottle webapp bottle.debug(True) bottle.run(app=app, quiet=False, reloader=True) if __name__ == "__main__": main() bottle-cork-0.12.0/examples/recreate_example_conf.py0000755000175000017500000000175312312136477022370 0ustar fedefede00000000000000#!/usr/bin/env python # # # Regenerate files in example_conf from datetime import datetime from cork import Cork def populate_conf_directory(): cork = Cork('example_conf', initialize=True) cork._store.roles['admin'] = 100 cork._store.roles['editor'] = 60 cork._store.roles['user'] = 50 cork._store.save_roles() tstamp = str(datetime.utcnow()) username = password = 'admin' cork._store.users[username] = { 'role': 'admin', 'hash': cork._hash(username, password), 'email_addr': username + '@localhost.local', 'desc': username + ' test user', 'creation_date': tstamp } username = password = '' cork._store.users[username] = { 'role': 'user', 'hash': cork._hash(username, password), 'email_addr': username + '@localhost.local', 'desc': username + ' test user', 'creation_date': tstamp } cork._store.save_users() if __name__ == '__main__': populate_conf_directory() bottle-cork-0.12.0/examples/simple_webapp_using_sqlite_mytest.py0000755000175000017500000001367112312136477025102 0ustar fedefede00000000000000#!/usr/bin/env python # # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under GPLv3+ license, see LICENSE.txt # # Cork example web application # # The following users are already available: # admin/admin, demo/demo import bottle from beaker.middleware import SessionMiddleware from cork import Cork from cork.backends import SQLiteBackend import logging logging.basicConfig(format='localhost - - [%(asctime)s] %(message)s', level=logging.DEBUG) log = logging.getLogger(__name__) bottle.debug(True) # Use users.json and roles.json in the local example_conf directory aaa = Cork('example_conf', email_sender='federico.ceratto@gmail.com', smtp_url='smtp://smtp.magnet.ie') def populate_backend(): b = SQLiteBackend('example2.db', initialize=True) b.connection.executescript(""" INSERT INTO users (username, email_addr, desc, role, hash, creation_date) VALUES ( 'admin', 'admin@localhost.local', 'admin test user', 'admin', 'cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=', '2012-10-28 20:50:26.286723' ); INSERT INTO roles (role, level) VALUES ('special', 200); INSERT INTO roles (role, level) VALUES ('admin', 100); INSERT INTO roles (role, level) VALUES ('editor', 60); INSERT INTO roles (role, level) VALUES ('user', 50); """) return b b = populate_backend() aaa = Cork(backend=b, email_sender='federico.ceratto@gmail.com', smtp_url='smtp://smtp.magnet.ie') app = bottle.app() session_opts = { 'session.cookie_expires': True, 'session.encrypt_key': 'please use a random key and keep it secret!', 'session.httponly': True, 'session.timeout': 3600 * 24, # 1 day 'session.type': 'cookie', 'session.validate_key': True, } app = SessionMiddleware(app, session_opts) # # Bottle methods # # def postd(): return bottle.request.forms def post_get(name, default=''): return bottle.request.POST.get(name, default).strip() @bottle.post('/login') def login(): """Authenticate users""" username = post_get('username') password = post_get('password') aaa.login(username, password, success_redirect='/', fail_redirect='/login') @bottle.route('/user_is_anonymous') def user_is_anonymous(): if aaa.user_is_anonymous: return 'True' return 'False' @bottle.route('/logout') def logout(): aaa.logout(success_redirect='/login') @bottle.post('/register') def register(): """Send out registration email""" aaa.register(post_get('username'), post_get('password'), post_get('email_address')) return 'Please check your mailbox.' @bottle.route('/validate_registration/:registration_code') def validate_registration(registration_code): """Validate registration, create user account""" aaa.validate_registration(registration_code) return 'Thanks. Go to login' @bottle.post('/reset_password') def send_password_reset_email(): """Send out password reset email""" aaa.send_password_reset_email( username=post_get('username'), email_addr=post_get('email_address') ) return 'Please check your mailbox.' @bottle.route('/change_password/:reset_code') @bottle.view('password_change_form') def change_password(reset_code): """Show password change form""" return dict(reset_code=reset_code) @bottle.post('/change_password') def change_password(): """Change password""" aaa.reset_password(post_get('reset_code'), post_get('password')) return 'Thanks. Go to login' @bottle.route('/') def index(): """Only authenticated users can see this""" aaa.require(fail_redirect='/login') return 'Welcome! Admin page Logout' @bottle.route('/restricted_download') def restricted_download(): """Only authenticated users can download this file""" aaa.require(fail_redirect='/login') return bottle.static_file('static_file', root='.') @bottle.route('/my_role') def show_current_user_role(): """Show current user role""" session = bottle.request.environ.get('beaker.session') print "Session from simple_webapp", repr(session) aaa.require(fail_redirect='/login') return aaa.current_user.role # Admin-only pages @bottle.route('/admin') @bottle.view('admin_page') def admin(): """Only admin users can see this""" aaa.require(role='admin', fail_redirect='/sorry_page') return dict( current_user=aaa.current_user, users=aaa.list_users(), roles=aaa.list_roles() ) @bottle.post('/create_user') def create_user(): try: aaa.create_user(postd().username, postd().role, postd().password) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_user') def delete_user(): try: aaa.delete_user(post_get('username')) return dict(ok=True, msg='') except Exception, e: print repr(e) return dict(ok=False, msg=e.message) @bottle.post('/create_role') def create_role(): try: aaa.create_role(post_get('role'), post_get('level')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_role') def delete_role(): try: aaa.delete_role(post_get('role')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) # Static pages @bottle.route('/login') @bottle.view('login_form') def login_form(): """Serve login form""" return {} @bottle.route('/sorry_page') def sorry_page(): """Serve sorry page""" return '

Sorry, you are not authorized to perform this action

' # # Web application main # # def main(): # Start the Bottle webapp bottle.debug(True) bottle.run(app=app, port=8877, quiet=False, reloader=False,server="paste") if __name__ == "__main__": main() bottle-cork-0.12.0/examples/simple_webapp_using_mongodb.py0000755000175000017500000001277012312136477023620 0ustar fedefede00000000000000#!/usr/bin/env python # # # Cork example web application # # The following users are already available: # admin/admin, demo/demo import bottle from beaker.middleware import SessionMiddleware from cork import Cork from cork.backends import MongoDBBackend import logging logging.basicConfig(format='localhost - - [%(asctime)s] %(message)s', level=logging.DEBUG) log = logging.getLogger(__name__) bottle.debug(True) def populate_mongodb_backend(): mb = MongoDBBackend(db_name='cork-example', initialize=True) mb.users._coll.insert({ "login": "admin", "email_addr": "admin@localhost.local", "desc": "admin test user", "role": "admin", "hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "creation_date": "2012-10-28 20:50:26.286723" }) mb.roles._coll.insert({'role': 'admin', 'val': 100}) mb.roles._coll.insert({'role': 'editor', 'val': 60}) mb.roles._coll.insert({'role': 'user', 'val': 50}) return mb mb = populate_mongodb_backend() aaa = Cork(backend=mb, email_sender='federico.ceratto@gmail.com', smtp_url='smtp://smtp.magnet.ie') app = bottle.app() session_opts = { 'session.cookie_expires': True, 'session.encrypt_key': 'please use a random key and keep it secret!', 'session.httponly': True, 'session.timeout': 3600 * 24, # 1 day 'session.type': 'cookie', 'session.validate_key': True, } app = SessionMiddleware(app, session_opts) # # Bottle methods # # def postd(): return bottle.request.forms def post_get(name, default=''): return bottle.request.POST.get(name, default).strip() @bottle.post('/login') def login(): """Authenticate users""" username = post_get('username') password = post_get('password') aaa.login(username, password, success_redirect='/', fail_redirect='/login') @bottle.route('/user_is_anonymous') def user_is_anonymous(): if aaa.user_is_anonymous: return 'True' return 'False' @bottle.route('/logout') def logout(): aaa.logout(success_redirect='/login') @bottle.post('/register') def register(): """Send out registration email""" aaa.register(post_get('username'), post_get('password'), post_get('email_address')) return 'Please check your mailbox.' @bottle.route('/validate_registration/:registration_code') def validate_registration(registration_code): """Validate registration, create user account""" aaa.validate_registration(registration_code) return 'Thanks. Go to login' @bottle.post('/reset_password') def send_password_reset_email(): """Send out password reset email""" aaa.send_password_reset_email( username=post_get('username'), email_addr=post_get('email_address') ) return 'Please check your mailbox.' @bottle.route('/change_password/:reset_code') @bottle.view('password_change_form') def change_password(reset_code): """Show password change form""" return dict(reset_code=reset_code) @bottle.post('/change_password') def change_password(): """Change password""" aaa.reset_password(post_get('reset_code'), post_get('password')) return 'Thanks. Go to login' @bottle.route('/') def index(): """Only authenticated users can see this""" aaa.require(fail_redirect='/login') return 'Welcome! Admin page Logout' @bottle.route('/restricted_download') def restricted_download(): """Only authenticated users can download this file""" aaa.require(fail_redirect='/login') return bottle.static_file('static_file', root='.') @bottle.route('/my_role') def show_current_user_role(): """Show current user role""" session = bottle.request.environ.get('beaker.session') print "Session from simple_webapp", repr(session) aaa.require(fail_redirect='/login') return aaa.current_user.role # Admin-only pages @bottle.route('/admin') @bottle.view('admin_page') def admin(): """Only admin users can see this""" aaa.require(role='admin', fail_redirect='/sorry_page') return dict( current_user=aaa.current_user, users=aaa.list_users(), roles=aaa.list_roles() ) @bottle.post('/create_user') def create_user(): try: aaa.create_user(postd().username, postd().role, postd().password) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_user') def delete_user(): try: aaa.delete_user(post_get('username')) return dict(ok=True, msg='') except Exception, e: print repr(e) return dict(ok=False, msg=e.message) @bottle.post('/create_role') def create_role(): try: aaa.create_role(post_get('role'), post_get('level')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_role') def delete_role(): try: aaa.delete_role(post_get('role')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) # Static pages @bottle.route('/login') @bottle.view('login_form') def login_form(): """Serve login form""" return {} @bottle.route('/sorry_page') def sorry_page(): """Serve sorry page""" return '

Sorry, you are not authorized to perform this action

' # # Web application main # # def main(): # Start the Bottle webapp bottle.debug(True) bottle.run(app=app, quiet=False, reloader=True) if __name__ == "__main__": main() bottle-cork-0.12.0/examples/static_file0000644000175000017500000000000312161350444017667 0ustar fedefede00000000000000hi bottle-cork-0.12.0/examples/cork.py0000644000175000017500000007466512312136477017025 0ustar fedefede00000000000000#!/usr/bin/env python # # Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # # This package is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 3 of the License, or (at your option) any later version. # # This package is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # from base64 import b64encode, b64decode from beaker import crypto from datetime import datetime, timedelta from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from logging import getLogger from smtplib import SMTP, SMTP_SSL from threading import Thread from time import time import bottle import os import re import uuid try: import scrypt scrypt_available = True except ImportError: # pragma: no cover scrypt_available = False from backends import JsonBackend log = getLogger(__name__) class AAAException(Exception): """Generic Authentication/Authorization Exception""" pass class AuthException(AAAException): """Authentication Exception: incorrect username/password pair""" pass class Cork(object): def __init__(self, directory=None, backend=None, email_sender=None, initialize=False, session_domain=None, smtp_server=None, smtp_url='localhost'): """Auth/Authorization/Accounting class :param directory: configuration directory :type directory: str. :param users_fname: users filename (without .json), defaults to 'users' :type users_fname: str. :param roles_fname: roles filename (without .json), defaults to 'roles' :type roles_fname: str. """ if smtp_server: smtp_url = smtp_server self.mailer = Mailer(email_sender, smtp_url) self.password_reset_timeout = 3600 * 24 self.session_domain = session_domain self.preferred_hashing_algorithm = 'PBKDF2' # Setup JsonBackend by default for backward compatibility. if backend is None: self._store = JsonBackend(directory, users_fname='users', roles_fname='roles', pending_reg_fname='register', initialize=initialize) else: self._store = backend def login(self, username, password, success_redirect=None, fail_redirect=None): """Check login credentials for an existing user. Optionally redirect the user to another page (typically /login) :param username: username :type username: str. :param password: cleartext password :type password: str. :param success_redirect: redirect authorized users (optional) :type success_redirect: str. :param fail_redirect: redirect unauthorized users (optional) :type fail_redirect: str. :returns: True for successful logins, else False """ assert isinstance(username, str), "the username must be a string" assert isinstance(password, str), "the password must be a string" if username in self._store.users: if self._verify_password(username, password, self._store.users[username]['hash']): # Setup session data self._setup_cookie(username) self._store.users[username]['last_login'] = str(datetime.utcnow()) self._store.save_users() if success_redirect: bottle.redirect(success_redirect) return True if fail_redirect: bottle.redirect(fail_redirect) return False def logout(self, success_redirect='/login', fail_redirect='/login'): """Log the user out, remove cookie :param success_redirect: redirect the user after logging out :type success_redirect: str. :param fail_redirect: redirect the user if it is not logged in :type fail_redirect: str. """ try: session = self._beaker_session session.delete() except Exception, e: log.debug("Exception %s while logging out." % repr(e)) bottle.redirect(fail_redirect) bottle.redirect(success_redirect) def require(self, username=None, role=None, fixed_role=False, fail_redirect=None): """Ensure the user is logged in has the required role (or higher). Optionally redirect the user to another page (typically /login) If both `username` and `role` are specified, both conditions need to be satisfied. If none is specified, any authenticated user will be authorized. By default, any role with higher level than `role` will be authorized; set fixed_role=True to prevent this. :param username: username (optional) :type username: str. :param role: role :type role: str. :param fixed_role: require user role to match `role` strictly :type fixed_role: bool. :param redirect: redirect unauthorized users (optional) :type redirect: str. """ # Parameter validation if username is not None: if username not in self._store.users: raise AAAException("Nonexistent user") if fixed_role and role is None: raise AAAException( """A role must be specified if fixed_role has been set""") if role is not None and role not in self._store.roles: raise AAAException("Role not found") # Authentication try: cu = self.current_user except AAAException: if fail_redirect is None: raise AuthException("Unauthenticated user") else: bottle.redirect(fail_redirect) # Authorization if cu.role not in self._store.roles: raise AAAException("Role not found for the current user") if username is not None: if username != self.current_user.username: if fail_redirect is None: raise AuthException("Unauthorized access: incorrect" " username") else: bottle.redirect(fail_redirect) if fixed_role: if role == self.current_user.role: return if fail_redirect is None: raise AuthException("Unauthorized access: incorrect role") else: bottle.redirect(fail_redirect) else: if role is not None: # Any role with higher level is allowed current_lvl = self._store.roles[self.current_user.role] threshold_lvl = self._store.roles[role] if current_lvl >= threshold_lvl: return if fail_redirect is None: raise AuthException("Unauthorized access: ") else: bottle.redirect(fail_redirect) return def create_role(self, role, level): """Create a new role. :param role: role name :type role: str. :param level: role level (0=lowest, 100=admin) :type level: int. :raises: AuthException on errors """ if self.current_user.level < 100: raise AuthException("The current user is not authorized to ") if role in self._store.roles: raise AAAException("The role is already existing") try: int(level) except ValueError: raise AAAException("The level must be numeric.") self._store.roles[role] = level self._store.save_roles() def delete_role(self, role): """Deleta a role. :param role: role name :type role: str. :raises: AuthException on errors """ if self.current_user.level < 100: raise AuthException("The current user is not authorized to ") if role not in self._store.roles: raise AAAException("Nonexistent role.") self._store.roles.pop(role) self._store.save_roles() def list_roles(self): """List roles. :returns: (role, role_level) generator (sorted by role) """ for role in sorted(self._store.roles): yield (role, self._store.roles[role]) def create_user(self, username, role, password, email_addr=None, description=None): """Create a new user account. This method is available to users with level>=100 :param username: username :type username: str. :param role: role :type role: str. :param password: cleartext password :type password: str. :param email_addr: email address (optional) :type email_addr: str. :param description: description (free form) :type description: str. :raises: AuthException on errors """ assert username, "Username must be provided." if self.current_user.level < 100: raise AuthException("The current user is not authorized" \ " to create users.") if username in self._store.users: raise AAAException("User is already existing.") if role not in self._store.roles: raise AAAException("Nonexistent user role.") tstamp = str(datetime.utcnow()) self._store.users[username] = { 'role': role, 'hash': self._hash(username, password), 'email_addr': email_addr, 'desc': description, 'creation_date': tstamp, 'last_login': tstamp } self._store.save_users() def delete_user(self, username): """Delete a user account. This method is available to users with level>=100 :param username: username :type username: str. :raises: Exceptions on errors """ if self.current_user.level < 100: raise AuthException("The current user is not authorized to ") if username not in self._store.users: raise AAAException("Nonexistent user.") self.user(username).delete() def list_users(self): """List users. :return: (username, role, email_addr, description) generator (sorted by username) """ for un in sorted(self._store.users): d = self._store.users[un] yield (un, d['role'], d['email_addr'], d['desc']) @property def current_user(self): """Current autenticated user :returns: User() instance, if authenticated :raises: AuthException otherwise """ session = self._beaker_session username = session.get('username', None) if username is None: raise AuthException("Unauthenticated user") if username is not None and username in self._store.users: return User(username, self, session=session) raise AuthException("Unknown user: %s" % username) @property def user_is_anonymous(self): """Check if the current user is anonymous. :returns: True if the user is anonymous, False otherwise :raises: AuthException if the session username is unknown """ try: username = self._beaker_session['username'] except KeyError: return True if username not in self._store.users: raise AuthException("Unknown user: %s" % username) return False def user(self, username): """Existing user :returns: User() instance if the user exist, None otherwise """ if username is not None and username in self._store.users: return User(username, self) return None def register(self, username, password, email_addr, role='user', max_level=50, subject="Signup confirmation", email_template='views/registration_email.tpl', description=None): """Register a new user account. An email with a registration validation is sent to the user. WARNING: this method is available to unauthenticated users :param username: username :type username: str. :param password: cleartext password :type password: str. :param role: role (optional), defaults to 'user' :type role: str. :param max_level: maximum role level (optional), defaults to 50 :type max_level: int. :param email_addr: email address :type email_addr: str. :param subject: email subject :type subject: str. :param email_template: email template filename :type email_template: str. :param description: description (free form) :type description: str. :raises: AssertError or AAAException on errors """ assert username, "Username must be provided." assert password, "A password must be provided." assert email_addr, "An email address must be provided." if username in self._store.users: raise AAAException("User is already existing.") if role not in self._store.roles: raise AAAException("Nonexistent role") if self._store.roles[role] > max_level: raise AAAException("Unauthorized role") registration_code = uuid.uuid4().hex creation_date = str(datetime.utcnow()) # send registration email email_text = bottle.template(email_template, username=username, email_addr=email_addr, role=role, creation_date=creation_date, registration_code=registration_code ) self.mailer.send_email(email_addr, subject, email_text) # store pending registration self._store.pending_registrations[registration_code] = { 'username': username, 'role': role, 'hash': self._hash(username, password), 'email_addr': email_addr, 'desc': description, 'creation_date': creation_date, } self._store.save_pending_registrations() def validate_registration(self, registration_code): """Validate pending account registration, create a new account if successful. :param registration_code: registration code :type registration_code: str. """ try: data = self._store.pending_registrations.pop(registration_code) except KeyError: raise AuthException("Invalid registration code.") username = data['username'] if username in self._store.users: raise AAAException("User is already existing.") # the user data is moved from pending_registrations to _users self._store.users[username] = { 'role': data['role'], 'hash': data['hash'], 'email_addr': data['email_addr'], 'desc': data['desc'], 'creation_date': data['creation_date'], 'last_login': str(datetime.utcnow()) } self._store.save_users() def send_password_reset_email(self, username=None, email_addr=None, subject="Password reset confirmation", email_template='views/password_reset_email'): """Email the user with a link to reset his/her password If only one parameter is passed, fetch the other from the users database. If both are passed they will be matched against the users database as a security check. :param username: username :type username: str. :param email_addr: email address :type email_addr: str. :param subject: email subject :type subject: str. :param email_template: email template filename :type email_template: str. :raises: AAAException on missing username or email_addr, AuthException on incorrect username/email_addr pair """ if username is None: if email_addr is None: raise AAAException("At least `username` or `email_addr` must" \ " be specified.") # only email_addr is specified: fetch the username for k, v in self._store.users.iteritems(): if v['email_addr'] == email_addr: username = k break else: raise AAAException("Email address not found.") else: # username is provided if username not in self._store.users: raise AAAException("Nonexistent user.") if email_addr is None: email_addr = self._store.users[username].get('email_addr', None) if not email_addr: raise AAAException("Email address not available.") else: # both username and email_addr are provided: check them stored_email_addr = self._store.users[username]['email_addr'] if email_addr != stored_email_addr: raise AuthException("Username/email address pair not found.") # generate a reset_code token reset_code = self._reset_code(username, email_addr) # send reset email email_text = bottle.template(email_template, username=username, email_addr=email_addr, reset_code=reset_code ) self.mailer.send_email(email_addr, subject, email_text) def reset_password(self, reset_code, password): """Validate reset_code and update the account password The username is extracted from the reset_code token :param reset_code: reset token :type reset_code: str. :param password: new password :type password: str. :raises: AuthException for invalid reset tokens, AAAException """ try: reset_code = b64decode(reset_code) username, email_addr, tstamp, h = reset_code.split(':', 3) tstamp = int(tstamp) except (TypeError, ValueError): raise AuthException("Invalid reset code.") if time() - tstamp > self.password_reset_timeout: raise AuthException("Expired reset code.") if not self._verify_password(username, email_addr, h): raise AuthException("Invalid reset code.") user = self.user(username) if user is None: raise AAAException("Nonexistent user.") user.update(pwd=password) def make_auth_decorator(self, username=None, role=None, fixed_role=False, fail_redirect='/login'): ''' Create a decorator to be used for authentication and authorization :param username: A resource can be protected for a specific user :param role: Minimum role level required for authorization :param fixed_role: Only this role gets authorized :param fail_redirect: The URL to redirect to if a login is required. ''' session_manager = self def auth_require(username=username, role=role, fixed_role=fixed_role, fail_redirect=fail_redirect): def decorator(func): import functools @functools.wraps(func) def wrapper(*a, **ka): session_manager.require(username=username, role=role, fixed_role=fixed_role, fail_redirect=fail_redirect) return func(*a, **ka) return wrapper return decorator return(auth_require) ## Private methods @property def _beaker_session(self): """Get Beaker session""" return bottle.request.environ.get('beaker.session') def _setup_cookie(self, username): """Setup cookie for a user that just logged in""" session = self._beaker_session session['username'] = username if self.session_domain is not None: session.domain = self.session_domain session.save() def _hash(self, username, pwd, salt=None, algo=None): """Hash username and password, generating salt value if required """ if algo is None: algo = self.preferred_hashing_algorithm if algo == 'PBKDF2': return self._hash_pbkdf2(username, pwd, salt=salt) if algo == 'scrypt': return self._hash_scrypt(username, pwd, salt=salt) raise RuntimeError("Unknown hashing algorithm requested: %s" % algo) @staticmethod def _hash_scrypt(username, pwd, salt=None): """Hash username and password, generating salt value if required Use scrypt. :returns: base-64 encoded str. """ if not scrypt_available: raise Exception("scrypt.hash required." " Please install the scrypt library.") if salt is None: salt = os.urandom(32) assert len(salt) == 32, "Incorrect salt length" cleartext = "%s\0%s" % (username, pwd) h = scrypt.hash(cleartext, salt) # 's' for scrypt return b64encode('s' + salt + h) @staticmethod def _hash_pbkdf2(username, pwd, salt=None): """Hash username and password, generating salt value if required Use PBKDF2 from Beaker :returns: base-64 encoded str. """ if salt is None: salt = os.urandom(32) assert len(salt) == 32, "Incorrect salt length" cleartext = "%s\0%s" % (username, pwd) h = crypto.generateCryptoKeys(cleartext, salt, 10) if len(h) != 32: raise RuntimeError("The PBKDF2 hash is %d bytes long instead" "of 32. The pycrypto library might be missing." % len(h)) # 'p' for PBKDF2 return b64encode('p' + salt + h) def _verify_password(self, username, pwd, salted_hash): """Verity username/password pair against a salted hash :returns: bool """ decoded = b64decode(salted_hash) hash_type = decoded[0] salt = decoded[1:33] if hash_type == 'p': # PBKDF2 h = self._hash_pbkdf2(username, pwd, salt) return salted_hash == h if hash_type == 's': # scrypt h = self._hash_scrypt(username, pwd, salt) return salted_hash == h raise RuntimeError("Unknown hashing algorithm: %s" % hash_type) def _purge_expired_registrations(self, exp_time=96): """Purge expired registration requests. :param exp_time: expiration time (hours) :type exp_time: float. """ for uuid, data in self._store.pending_registrations.items(): creation = datetime.strptime(data['creation_date'], "%Y-%m-%d %H:%M:%S.%f") now = datetime.utcnow() maxdelta = timedelta(hours=exp_time) if now - creation > maxdelta: self._store.pending_registrations.pop(uuid) def _reset_code(self, username, email_addr): """generate a reset_code token :param username: username :type username: str. :param email_addr: email address :type email_addr: str. :returns: Base-64 encoded token """ h = self._hash(username, email_addr) t = "%d" % time() reset_code = ':'.join((username, email_addr, t, h)) return b64encode(reset_code) class User(object): def __init__(self, username, cork_obj, session=None): """Represent an authenticated user, exposing useful attributes: username, role, level, description, email_addr, session_creation_time, session_accessed_time, session_id. The session-related attributes are available for the current user only. :param username: username :type username: str. :param cork_obj: instance of :class:`Cork` """ self._cork = cork_obj assert username in self._cork._store.users, "Unknown user" self.username = username user_data = self._cork._store.users[username] self.role = user_data['role'] self.description = user_data['desc'] self.email_addr = user_data['email_addr'] self.level = self._cork._store.roles[self.role] if session is not None: try: self.session_creation_time = session['_creation_time'] self.session_accessed_time = session['_accessed_time'] self.session_id = session['_id'] except: pass def update(self, role=None, pwd=None, email_addr=None): """Update an user account data :param role: change user role, if specified :type role: str. :param pwd: change user password, if specified :type pwd: str. :param email_addr: change user email address, if specified :type email_addr: str. :raises: AAAException on nonexistent user or role. """ username = self.username if username not in self._cork._store.users: raise AAAException("User does not exist.") if role is not None: if role not in self._cork._store.roles: raise AAAException("Nonexistent role.") self._cork._store.users[username]['role'] = role if pwd is not None: self._cork._store.users[username]['hash'] = self._cork._hash( username, pwd) if email_addr is not None: self._cork._store.users[username]['email_addr'] = email_addr self._cork._store.save_users() def delete(self): """Delete user account :raises: AAAException on nonexistent user. """ try: self._cork._store.users.pop(self.username) except KeyError: raise AAAException("Nonexistent user.") self._cork._store.save_users() class Mailer(object): def __init__(self, sender, smtp_url, join_timeout=5): """Send emails asyncronously :param sender: Sender email address :type sender: str. :param smtp_server: SMTP server :type smtp_server: str. """ self.sender = sender self.join_timeout = join_timeout self._threads = [] self._conf = self._parse_smtp_url(smtp_url) def _parse_smtp_url(self, url): """Parse SMTP URL""" match = re.match(r""" ( # Optional protocol (?Psmtp|starttls|ssl) # Protocol name :// )? ( # Optional user:pass@ (?P[^:]*) # Match every char except ':' (: (?P.*) )? @ # Optional :pass )? (?P # Required FQDN on IP address ()| # Empty string ( # FQDN [a-zA-Z_\-] # First character cannot be a number [a-zA-Z0-9_\-\.]{,254} ) |( # IPv4 ([0-9]{1,3}\.){3} [0-9]{1,3} ) |( # IPv6 \[ # Square brackets ([0-9a-f]{,4}:){1,8} [0-9a-f]{,4} \] ) ) ( # Optional :port : (?P[0-9]{,5}) # Up to 5-digits port )? [/]? $ """, url, re.VERBOSE) if not match: raise RuntimeError("SMTP URL seems incorrect") d = match.groupdict() if d['proto'] is None: d['proto'] = 'smtp' if d['port'] is None: d['port'] = 25 else: d['port'] = int(d['port']) if not 0 < d['port'] < 65536: raise RuntimeError("Incorrect SMTP port") return d def send_email(self, email_addr, subject, email_text): """Send an email :param email_addr: email address :type email_addr: str. :param subject: subject :type subject: str. :param email_text: email text :type email_text: str. :raises: AAAException if smtp_server and/or sender are not set """ if not (self._conf['fqdn'] and self.sender): raise AAAException("SMTP server or sender not set") msg = MIMEMultipart('alternative') msg['Subject'] = subject msg['From'] = self.sender msg['To'] = email_addr part = MIMEText(email_text, 'html') msg.attach(part) log.debug("Sending email using %s" % self._conf['fqdn']) thread = Thread(target=self._send, args=(email_addr, msg.as_string())) thread.start() self._threads.append(thread) def _send(self, email_addr, msg): """Deliver an email using SMTP :param email_addr: recipient :type email_addr: str. :param msg: email text :type msg: str. """ proto = self._conf['proto'] assert proto in ('smtp', 'starttls', 'ssl'), \ "Incorrect protocol: %s" % proto try: if proto == 'ssl': log.debug("Setting up SSL") session = SMTP_SSL(self._conf['fqdn'], self._conf['port']) else: session = SMTP(self._conf['fqdn'], self._conf['port']) if proto == 'starttls': log.debug('Sending EHLO and STARTTLS') session.ehlo() session.starttls() session.ehlo() if self._conf['user'] is not None: log.debug('Performing login') session.login(self._conf['user'], self._conf['pass']) log.debug('Sending') session.sendmail(self.sender, email_addr, msg) session.quit() log.info('Email sent') except Exception as e: # pragma: no cover log.error("Error sending email: %s" % e, exc_info=True) def join(self): """Flush email queue by waiting the completion of the existing threads :returns: None """ return [t.join(self.join_timeout) for t in self._threads] def __del__(self): """Class destructor: wait for threads to terminate within a timeout""" self.join() bottle-cork-0.12.0/examples/templates/0000755000175000017500000000000012617467123017474 5ustar fedefede00000000000000bottle-cork-0.12.0/examples/templates/password_change_form.html0000644000175000017500000000147012161350444024545 0ustar fedefede00000000000000

Password change

Please insert your new password:




bottle-cork-0.12.0/examples/templates/login_form.html0000644000175000017500000000357112166632355022523 0ustar fedefede00000000000000

Login

Please insert your credentials:




Signup

Please insert your credentials:




Password reset

Please insert your credentials:





bottle-cork-0.12.0/examples/templates/password_reset_email.html0000644000175000017500000000042412161350444024564 0ustar fedefede00000000000000Hello {{username}},
You are receiving this email because you requested a password reset on Cork Demo Webapp.

If you wish to complete the password reset please click on:
Confirm

bottle-cork-0.12.0/examples/templates/registration_email.html0000644000175000017500000000056112161350444024234 0ustar fedefede00000000000000Hello {{username}},
You are receiving this email because you registered on Cork Demo Webapp on {{creation_date}}.

If you wish to complete the registration please click on:
Confirm

Your email address is: {{email_addr}}
Your role will be: {{role}}.
bottle-cork-0.12.0/examples/templates/admin_page.html0000644000175000017500000000765012302755331022446 0ustar fedefede00000000000000

Cork - Administration page

Welcome {{current_user.username}}, your role is: {{current_user.role}}, access time: {{current_user.session_accessed_time}}

Create new user:


Delete user:


Create new role:


Delete role:

{% for u in users: %} {% endfor %}
UsernameRoleEmailDescription
{{u[0]}}{{u[1]}}{{u[2]}}{{u[2]}}

{% for r in roles: %} {% endfor %}
RoleLevel
{{r[0]}}{{r[1]}}

(Reload page to refresh)

Ready.

bottle-cork-0.12.0/examples/test_real_ssl_smtp.py0000644000175000017500000000060112312136477021750 0ustar fedefede00000000000000from cork import Cork import logging logging.basicConfig(format='localhost - - [%(asctime)s] %(message)s', level=logging.DEBUG) log = logging.getLogger(__name__) aaa = Cork('example_conf', email_sender='federico.ceratto@gmail.com', smtp_url='ssl://federico.ceratto@gmail.com:jckgtwesxgfrrqfi@smtp.gmail.com:587' ) aaa.register('user', 'pass', 'federico.ceratto@gmail.com') bottle-cork-0.12.0/examples/example.db0000644000175000017500000001600012461704342017427 0ustar fedefede00000000000000SQLite format 3@ - .|#tableregisterregisterCREATE TABLE register (code TEXT PRIMARY KEY ASC,username TEXT ,role TEXT ,hash TEXT ,email_addr TEXT ,desc TEXT ,creation_date TEXT )/Cindexsqlite_autoindex_register_1registerTtablerolesrolesCREATE TABLE roles (role TEXT PRIMARY KEY ASC,level INTEGER ))=indexsqlite_autoindex_roles_1roles tableusersusersCREATE TABLE users (username TEXT PRIMARY KEY ASC,role TEXT ,hash TEXT ,email_addr TEXT ,desc TEXT ,creation_date TEXT ,last_login TEXT ))=indexsqlite_autoindex_users_1users TT) =7+AadminadmincLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=admin@localhost.localadmin test user2012-10-28 20:50:26.286723  admin user2 editor< admind special user editor admin  special  bottle-cork-0.12.0/examples/simple_webapp_using_sqlite.py0000755000175000017500000001336112314377543023474 0ustar fedefede00000000000000#!/usr/bin/env python # # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under GPLv3+ license, see LICENSE.txt # # Cork example web application # # The following users are already available: # admin/admin, demo/demo import bottle from beaker.middleware import SessionMiddleware from cork import Cork from cork.backends import SQLiteBackend import logging logging.basicConfig(format='localhost - - [%(asctime)s] %(message)s', level=logging.DEBUG) log = logging.getLogger(__name__) bottle.debug(True) def populate_backend(): b = SQLiteBackend('example.db', initialize=True) b.connection.executescript(""" INSERT INTO users (username, email_addr, desc, role, hash, creation_date) VALUES ( 'admin', 'admin@localhost.local', 'admin test user', 'admin', 'cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=', '2012-10-28 20:50:26.286723' ); INSERT INTO roles (role, level) VALUES ('special', 200); INSERT INTO roles (role, level) VALUES ('admin', 100); INSERT INTO roles (role, level) VALUES ('editor', 60); INSERT INTO roles (role, level) VALUES ('user', 50); """) return b b = populate_backend() aaa = Cork(backend=b, email_sender='federico.ceratto@gmail.com', smtp_url='smtp://smtp.magnet.ie') app = bottle.app() session_opts = { 'session.cookie_expires': True, 'session.encrypt_key': 'please use a random key and keep it secret!', 'session.httponly': True, 'session.timeout': 3600 * 24, # 1 day 'session.type': 'cookie', 'session.validate_key': True, } app = SessionMiddleware(app, session_opts) # # Bottle methods # # def postd(): return bottle.request.forms def post_get(name, default=''): return bottle.request.POST.get(name, default).strip() @bottle.post('/login') def login(): """Authenticate users""" username = post_get('username') password = post_get('password') aaa.login(username, password, success_redirect='/', fail_redirect='/login') @bottle.route('/user_is_anonymous') def user_is_anonymous(): if aaa.user_is_anonymous: return 'True' return 'False' @bottle.route('/logout') def logout(): aaa.logout(success_redirect='/login') @bottle.post('/register') def register(): """Send out registration email""" aaa.register(post_get('username'), post_get('password'), post_get('email_address')) return 'Please check your mailbox.' @bottle.route('/validate_registration/:registration_code') def validate_registration(registration_code): """Validate registration, create user account""" aaa.validate_registration(registration_code) return 'Thanks. Go to login' @bottle.post('/reset_password') def send_password_reset_email(): """Send out password reset email""" aaa.send_password_reset_email( username=post_get('username'), email_addr=post_get('email_address') ) return 'Please check your mailbox.' @bottle.route('/change_password/:reset_code') @bottle.view('password_change_form') def change_password(reset_code): """Show password change form""" return dict(reset_code=reset_code) @bottle.post('/change_password') def change_password(): """Change password""" aaa.reset_password(post_get('reset_code'), post_get('password')) return 'Thanks. Go to login' @bottle.route('/') def index(): """Only authenticated users can see this""" aaa.require(fail_redirect='/login') return 'Welcome! Admin page Logout' @bottle.route('/restricted_download') def restricted_download(): """Only authenticated users can download this file""" aaa.require(fail_redirect='/login') return bottle.static_file('static_file', root='.') @bottle.route('/my_role') def show_current_user_role(): """Show current user role""" session = bottle.request.environ.get('beaker.session') print "Session from simple_webapp", repr(session) aaa.require(fail_redirect='/login') return aaa.current_user.role # Admin-only pages @bottle.route('/admin') @bottle.view('admin_page') def admin(): """Only admin users can see this""" aaa.require(role='admin', fail_redirect='/sorry_page') return dict( current_user=aaa.current_user, users=aaa.list_users(), roles=aaa.list_roles() ) @bottle.post('/create_user') def create_user(): try: aaa.create_user(postd().username, postd().role, postd().password) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_user') def delete_user(): try: aaa.delete_user(post_get('username')) return dict(ok=True, msg='') except Exception, e: print repr(e) return dict(ok=False, msg=e.message) @bottle.post('/create_role') def create_role(): try: aaa.create_role(post_get('role'), post_get('level')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_role') def delete_role(): try: aaa.delete_role(post_get('role')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) # Static pages @bottle.route('/login') @bottle.view('login_form') def login_form(): """Serve login form""" return {} @bottle.route('/sorry_page') def sorry_page(): """Serve sorry page""" return '

Sorry, you are not authorized to perform this action

' # # Web application main # # def main(): # Start the Bottle webapp bottle.debug(True) bottle.run(app=app, quiet=False, reloader=False) if __name__ == "__main__": main() bottle-cork-0.12.0/examples/example_conf/0000755000175000017500000000000012617467123020136 5ustar fedefede00000000000000bottle-cork-0.12.0/examples/example_conf/register.json0000644000175000017500000000000212166632355022645 0ustar fedefede00000000000000{}bottle-cork-0.12.0/examples/example_conf/users.json0000644000175000017500000000072212512453671022170 0ustar fedefede00000000000000{"admin": {"hash": "cLzRnzbEwehP6ZzTREh3A4MXJyNo+TV8Hs4//EEbPbiDoo+dmNg22f2RJC282aSwgyWv/O6s3h42qrA6iHx8yfw=", "email_addr": "admin@localhost.local", "role": "admin", "creation_date": "2012-10-28 20:50:26.286723", "desc": "admin test user"}, "": {"hash": "cFFusHJ5BZ07G3KhrkeVjB6pIyIGoMP6+BtK0JBtpU/EjhY6v3nvnOZgLqoniJQ9OXn2KFV+SJmZSyPH4Og0HiQ=", "email_addr": "@localhost.local", "role": "user", "creation_date": "2012-10-28 20:50:26.286723", "desc": " test user"}}bottle-cork-0.12.0/examples/example_conf/roles.json0000644000175000017500000000005012302756620022142 0ustar fedefede00000000000000{"admin": 100, "editor": 60, "user": 50}bottle-cork-0.12.0/examples/cork/0000755000175000017500000000000012617467123016434 5ustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/base_backend.py0000644000175000017500000000000012512453671030203 1bottle-cork-0.12.0/cork/base_backend.pyustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/SUMMARY0000644000175000017500000000011212312671642017501 0ustar fedefede00000000000000 2 3 test.py k mongo k mysql k sqlalch k paralle k sqlite k bottle-cork-0.12.0/examples/cork/mongodb_backend.py0000644000175000017500000000000012520166131031420 1bottle-cork-0.12.0/cork/mongodb_backend.pyustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/__pycache__/0000755000175000017500000000000012617467123020644 5ustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/__pycache__/__init__.cpython-34.pyc0000644000175000017500000000047712617463330025033 0ustar fedefede00000000000000 e>VS@s8ddlmZmZmZmZmZmZmZdS))Cork JsonBackend AAAException AuthExceptionMailer FlaskCorkRedirectN)corkrrrrrrrr r >/home/fede/newhome/projects/bottle-cork/tests/cork/__init__.pysbottle-cork-0.12.0/examples/cork/__pycache__/base_backend.cpython-34.pyc0000644000175000017500000000226112516462076025652 0ustar fedefede00000000000000 W*U@sXdZGdddeZddZGdddeZGdddeZd S) z4 .. module:: backend.py :synopsis: Base Backend. c@seZdZdZdS)BackendIOExceptionzGeneric Backend I/O ExceptionN)__name__ __module__ __qualname____doc__rrB/home/fede/newhome/projects/bottle-cork/tests/cork/base_backend.pyr s rcOs tdS)N)NotImplementedError)argskwargsrrrnisr c@s(eZdZdZeZeZeZdS)Backendz7Base Backend class - to be subclassed by real backends.N)rrrrr save_users save_rolessave_pending_registrationsrrrrr s r c@s:eZdZdZeZeZeZeZeZ eZ dS)Tablez5Base Table class - to be subclassed by real backends.N) rrrrr __len__ __contains__ __setitem__ __getitem____iter__ iteritemsrrrrrs rN)r Exceptionrr objectr rrrrrs bottle-cork-0.12.0/examples/cork/__pycache__/json_backend.cpython-34.pyc0000644000175000017500000001150612520666144025710 0ustar fedefede00000000000000 Ds$       bottle-cork-0.12.0/examples/cork/__pycache__/sqlite_backend.cpython-34.pyc0000644000175000017500000002337212617434521026243 0ustar fedefede00000000000000 sU@sdZddlmZddlmZeeZGdddeZGdddej Z Gd d d e Z Gd d d e Z Gd dde Z Gddde Z GdddejZdS)zB .. module:: sqlite_backend :synopsis: SQLite storage backend. ) base_backend) getLoggerc@s(eZdZddZddZdS) SqlRowProxycCsYddt|jdd|ddD}tj||||_||_dS)Ncss'|]\\}}}||fVqdS)N).0kZktypevrrD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.py sz'SqlRowProxy.__init__..r)zip_columnsdict__init___table_key)selftablekeyrowZlirrr rs3 zSqlRowProxy.__init__cCs'tj|||||j|j"s z"Table.__init__..r) _backend connection_engine _table_namer _column_names _key_col_numZ _key_col_name_key_col)rbackend table_namerrr rs    zTable.__init__cCs>t|tst||j}t|||}||fS)N) isinstancetupleAssertionErrorr$r)rrrrow_key row_valuerrr _row_to_value's zTable._row_to_valuecCs-d|j}|jj|}|jdS)NzSELECT count() FROM %sr)r"r run_queryfetchone)rqueryretrrr __len__-s z Table.__len__cCs5d|j|j|f}|jj|}|dk S)NzSELECT * FROM %s WHERE %s='%s')r"r%r fetch_one)rrr0rrrr __contains__2szTable.__contains__c sgttsttt|jdd}}|| s\tt|||| s}tt||tt|jddkstdtttt|jddf|gfdd|jddD}dj|j}djdd|D}d |j||f}|jj ||} dS) zCreate or update a rowrNz%s %scsg|]}|qSrr)rr)rrr rCs z%Table.__setitem__..z, css|] }dVqdS)?Nr)rxrrr r Fsz$Table.__setitem__..z*INSERT OR REPLACE INTO %s (%s) VALUES (%s)) r(rr*setr#reprjoinr"rrun_query_using_conversion) rrrr Zcn col_valuesZ col_namesZquestion_marksr0r1r)rr r9s&!!+5-zTable.__setitem__cCsZd|j|j|f}|jj|}|dkrFt|n|j||dS)NzSELECT * FROM %s WHERE %s='%s'r)r"r%rr3KeyErrorr-)rrr0rrrr __getitem__Ms  zTable.__getitem__ccsFd|j|jf}|jj|}x|D]}|dVq/WdS)z#Iterate over table index key valueszSELECT %s FROM %srN)r%r"rr.)rr0resultrrrr __iter__Ws zTable.__iter__ccsjd|j}|jj|}xD|D]<}tt|j|}|j|j|j|fVq&WdS)zIterate over table rowszSELECT * FROM %sN)r"rr.rr r#popr%)rr0r>rdrrr iteritems^s   zTable.iteritemscCs<|j|}d|j|j|f}|jj||S)NzDELETE FROM %s WHERE %s='%s')r=r"r%rr3)rrrAr0rrr r@hs z Table.popcCs tdS)N)NotImplementedError)rrArrr insertpsz Table.insertcCs tdS)N)rC)rrrr empty_tablesszTable.empty_tablecCsg}xy|jD]n\}}|tkr1d}n|tkrFd}n||jkr^d}nd}|jd|||fqWdj|}d|j|f}|jj|dS) zIssue table creationINTEGERTEXTzPRIMARY KEY ASCz%s %s %s,zCREATE TABLE %s (%s)N) r intstrr%appendr9r"rr.)rZcccol_nameZcol_typeextrasr0rrr create_tablevs     zTable.create_tableN)rrr__doc__rr-r2r4rr=r?rBr@rDrErOrrrr rs         rcs:eZdZfddZddZddZS)SingleValueTablecs*tt|j||jd|_dS)Nr)superrQrr# _value_col)rargs) __class__rr rszSingleValueTable.__init__cCsWt|t std|j|j|jf}||f}|jj||}dS)zCreate or update a rowz0INSERT OR REPLACE INTO %s (%s, %s) VALUES (?, ?)N)r(rr*r"r%rSrr:)rrrr0r;r1rrr rs  zSingleValueTable.__setitem__cCsTd|j|j|j|f}|jj|}|dkrLt|n|dS)NzSELECT %s FROM %s WHERE %s='%s'r)rSr"r%rr3r<)rrr0rrrr r=s  zSingleValueTable.__getitem__)rrrrrr=rr)rUr rQs  rQcs"eZdZfddZS) UsersTablecsedtfdtfdtfdtfdtfdtfdtff|_tt|j||dS)Nusernamerolehash email_addrdesc creation_date last_login)rKr rRrVr)rrTkwargs)rUrr rs      zUsersTable.__init__)rrrrrr)rUr rVs rVcs"eZdZfddZS) RolesTablecs8dtfdtff|_tt|j||dS)NrXlevel)rKrJr rRr_r)rrTr^)rUrr rs zRolesTable.__init__)rrrrrr)rUr r_s r_cs"eZdZfddZS)PendingRegistrationsTablecsedtfdtfdtfdtfdtfdtfdtff|_tt|j||dS)NcoderWrXrYrZr[r\)rKr rRrar)rrTr^)rUrr rs      z"PendingRegistrationsTable.__init__)rrrrrr)rUr ras rac@seZdZddddddZeddZd d Zd d Zd dZddZ ddZ ddZ ddZ ddZ dS) SQLiteBackendusersrolesregisterFcCs||_t|||_t|||_t|||_|r||jj|jj|jjtj dndS)NzTables created) _filenamerVrdr_rerapending_registrationsrOlogdebug)rfilename users_tname roles_tnamepending_reg_tname initializerrr rs    zSQLiteBackend.__init__c CsRy |jSWn@tk rMddl}|j|jdd|_|jSYnXdS)Nrisolation_level) _connectionAttributeErrorsqlite3connectrg)rrsrrr r s    zSQLiteBackend.connectioncCs|jj|S)N)rqexecute)rr0rrr r.szSQLiteBackend.run_querycCs|jj||S)N)rqru)rr0rTrrr r:sz(SQLiteBackend.run_query_using_conversioncCs|jj|jS)N)rqrur/)rr0rrr r3szSQLiteBackend.fetch_onecCs tdS)N)rC)rdb_namerrr _initialize_storagesz!SQLiteBackend._initialize_storagecCs tdS)N)rC)rrrr _drop_all_tablesszSQLiteBackend._drop_all_tablescCsdS)Nr)rrrr save_usersszSQLiteBackend.save_userscCsdS)Nr)rrrr save_rolesszSQLiteBackend.save_rolescCsdS)Nr)rrrr save_pending_registrationssz(SQLiteBackend.save_pending_registrationsN)rrrrpropertyr r.r:r3rwrxryrzr{rrrr rcs        rcN)rPrHrloggingrrrirrrrQrVr_raBackendrcrrrr s  p bottle-cork-0.12.0/examples/cork/__pycache__/mongodb_backend.cpython-34.pyc0000644000175000017500000001547312520666144026373 0ustar fedefede00000000000000 Y@U  @sdZddlmZeeZddlmZmZy#ddlZej ddkZ Wne k roYnXGdddeZ Gd d d e Z Gd d d eZGd dde ZGdddeZdS)zD .. module:: mongodb_backend :synopsis: MongoDB storage backend. ) getLogger)BackendTableNc@sjeZdZdZddZddZddZdd Zd d Zd d Z ddZ dS) MongoTablez>Abstract MongoDB Table. Allow dictionary-like access. cCs||_||_||_dS)N)_name _key_name_coll)selfnamekey_name collectionrE/home/fede/newhome/projects/bottle-cork/tests/cork/mongodb_backend.py__init__s  zMongoTable.__init__cCs#|jj|jdddddS)zCreate collection index.Z drop_dupsTuniqueN)r create_indexr )r rrrrs  zMongoTable.create_indexcCs |jjS)N)r count)r rrr__len__&szMongoTable.__len__cCs&|jji||j6}|dk S)N)r find_oner )r valuerrrr __contains__)szMongoTable.__contains__csVtr$jjdjg}njjdjg}fdd|DS)zIter on dictionary keysfieldsZ projectionc3s|]}|jVqdS)N)r ).0i)r rr 4sz&MongoTable.__iter__..) is_pymongo_2r findr )r rr)r r__iter__-szMongoTable.__iter__ccs_|jj}xI|D]A}|j}|j|j|jd||j|fVqWdS)zVIter on dictionary items. :returns: generator of (key, value) tuples _idN)r rcopypopr )r rrdrrr iteritems6s    zMongoTable.iteritemscCs.||}|jji||j6dd|S)zRemove a dictionary itemwr)r remover )r key_valrrrrr#Bs  zMongoTable.popN) __name__ __module__ __qualname____doc__rrrrr r%r#rrrrrs      rcs@eZdZdZfddZddZddZS)MongoSingleValueTablez[MongoDB table accessible as a simple key -> value dictionary. Used to store roles. cstt|j||dS)N)superr-r)r argskw) __class__rrrNszMongoSingleValueTable.__init__cCst|t sti||j6}i||j6|d6}trl|jj|i|d6ddddn |jj|i|d6dddS)Nvalz$setupsertTr&r) isinstancedictAssertionErrorr rr update update_one)r r(dataspecrrr __setitem__Qs )z!MongoSingleValueTable.__setitem__cCs?|jji||j6}|dkr7t|n|dS)Nr2)r rr KeyError)r r(rrrr __getitem__Zs z!MongoSingleValueTable.__getitem__)r)r*r+r,rr;r=rr)r1rr-Is  r-cs:eZdZdZfddZfddZS)MongoMutableDictz:Represent an item from a Table. Acts as a dictionary. cs,tt|j|||_||_dS)z|Create a MongoMutableDict instance. :param parent: Table instance :type parent: :class:`MongoTable` N)r.r>r_parent _root_key)r parentZroot_keyr$)r1rrrds zMongoMutableDict.__init__cstt|j||i|j|jj6}trd|jjj|ii||6d6dd}n,|jjj |ii||6d6dd}dS)Nz$setr3T) r.r>r;r@r?r rr r7r8)r kvr:r)r1rrr;ms /zMongoMutableDict.__setitem__)r)r*r+r,rr;rr)r1rr>as  r>cs@eZdZdZfddZddZddZS)MongoMultiValueTablez.MongoDB table accessible as a dictionary. cstt|j||dS)N)r.rDr)r r/r0)r1rrrzszMongoMultiValueTable.__init__cCst|tst|j}||krC|||ksMtn |||)r r(rrrrr=s z MongoMultiValueTable.__getitem__)r)r*r+r,rr;r=rr)r1rrDws  rDc@s^eZdZddddddddZdd Zd d Zd d ZddZdS)MongoDBBackendcork localhostiiFNc Cstjd|d|}||}|rA|rA|j||ntdd|j|_tdd|j|_tdd|j|_|r|jnd S) zInitialize MongoDB Backendhostportusersloginpending_registrationsZpending_registrationrolesroleN) pymongoZ MongoClientZ authenticaterDrJrLr-rM_initialize_storage) r Zdb_namehostnamerI initializeusernamepassword connectiondbrrrrs  zMongoDBBackend.__init__cCs1x*|j|j|jfD]}|jqWdS)zCreate MongoDB indexes.N)rJrMrLr)r crrrrPsz"MongoDBBackend._initialize_storagecCsdS)Nr)r rrr save_usersszMongoDBBackend.save_userscCsdS)Nr)r rrr save_rolesszMongoDBBackend.save_rolescCsdS)Nr)r rrrsave_pending_registrationssz)MongoDBBackend.save_pending_registrations)r)r*r+rrPrXrYrZrrrrrEs    rE)r,loggingrr)log base_backendrrrOZ version_tupler ImportErrorrr-r5r>rDrErrrrs   4bottle-cork-0.12.0/examples/cork/__pycache__/backends.cpython-34.pyc0000644000175000017500000000075312520666144025044 0ustar fedefede00000000000000 D/home/fede/newhome/projects/bottle-cork/tests/cork/backends.pysbottle-cork-0.12.0/examples/cork/__pycache__/cork.cpython-34.pyc0000644000175000017500000007257012617465336024245 0ustar fedefede00000000000000 @j>V@sXddlmZmZddlmZmZddlmZddlmZddl m Z ddl m Z m Z ddlmZddlmZdd lZdd lZdd lZdd lZdd lZdd lZejdd d d d fkredejdnydd lZdZWnek rFdZYnXyeWnek rieZYnXddl m!Z!ejj"d kZ#e e$Z%Gddde&Z'Gddde'Z(Gddde)Z*Gddde)Z+Gddde&Z,ddZ-Gdd d e*Z.Gd!d"d"e*Z/Gd#d$d$e)Z0d S)%) b64encode b64decode)datetime timedelta) MIMEMultipart)MIMEText) getLogger)SMTPSMTP_SSL)Thread)timeNzPython >= 2.7.8 is requiredTF) JsonBackendc@seZdZdZdS) AAAExceptionz.Generic Authentication/Authorization ExceptionN)__name__ __module__ __qualname____doc__rr:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyr9s rc@seZdZdZdS) AuthExceptionz:Authentication Exception: incorrect username/password pairN)rrrrrrrrr>s rc @seZdZdZddddddddddZddddZd d d d Zddddd d ZddZddZ ddZ ddddZ ddZ ddZ eddZeddZddZd d!d"d#dd$d%Zd&d'Zddd(d)d*d+Zd,d-Zdddd d.d/Zd0d1Zddd2d3Zedd4d5Zedd6d7Zd8d9Zd:d;d<Zd=d>ZdS)?BaseCorkzAbstract classNF localhostc Cs|r|}nt|||_d |_||_|p<d|_d|_|dkrt|dddd d d d ||_n ||_dS)a]Auth/Authorization/Accounting class :param directory: configuration directory :type directory: str. :param users_fname: users filename (without .json), defaults to 'users' :type users_fname: str. :param roles_fname: roles filename (without .json), defaults to 'roles' :type roles_fname: str. izbeaker.sessionPBKDF2NZ users_fnameusersZ roles_fnamerolesZpending_reg_fnameregister initializeiQ)Mailermailerpassword_reset_timeoutsession_domainsession_key_namepreferred_hashing_algorithmr_store) selfZ directoryZbackend email_senderr"r& smtp_serversmtp_urlr'rrr__init__Fs        zBaseCork.__init__cCs||jjkr|jj|d}t|drG|jd}n|j|||}|r|j|ttj|jj|d<|jj |r|j |ndSn|r|j |ndS)a'Check login credentials for an existing user. Optionally redirect the user to another page (typically /login) :param username: username :type username: str or unicode. :param password: cleartext password :type password: str.or unicode :param success_redirect: redirect authorized users (optional) :type success_redirect: str. :param fail_redirect: redirect unauthorized users (optional) :type fail_redirect: str. :returns: True for successful logins, else False hashencodeascii last_loginTF) r)rhasattrr0_verify_password _setup_cookiestrrutcnow save_users _redirect)r*usernamepasswordsuccess_redirect fail_redirect salted_hashZ authenticatedrrrlogines&   zBaseCork.loginz/logincCsty|j}|jWnItk rb}z)tjdt||j|WYdd}~XnX|j|dS)zLog the user out, remove cookie :param success_redirect: redirect the user after logging out :type success_redirect: str. :param fail_redirect: redirect the user if it is not logged in :type fail_redirect: str. zException %s while logging out.N)_beaker_sessiondelete Exceptionlogdebugreprr9)r*r<r=sessionerrrlogouts  zBaseCork.logoutc Cs|dk r0||jjkr0tdq0n|rQ|dkrQtdn|dk r~||jjkr~tdny |j}Wn:tk r|dkrtdn |j|YnX|j|jjkrtdn|dk r9||jjkrdS|dkr)tdn|j|n|r||jjkrUdS|dkrptdn|j|n|dk r|jj|jj}|jj|}||krdS|dkrtd n|j|ndS) aEnsure the user is logged in has the required role (or higher). Optionally redirect the user to another page (typically /login) If both `username` and `role` are specified, both conditions need to be satisfied. If none is specified, any authenticated user will be authorized. By default, any role with higher level than `role` will be authorized; set fixed_role=True to prevent this. :param username: username (optional) :type username: str. :param role: role :type role: str. :param fixed_role: require user role to match `role` strictly :type fixed_role: bool. :param redirect: redirect unauthorized users (optional) :type redirect: str. NzNonexistent userz3A role must be specified if fixed_role has been setzRole not foundzUnauthenticated userz#Role not found for the current userz'Unauthorized access: incorrect usernamez#Unauthorized access: incorrect rolezUnauthorized access: ) r)rrr current_userrr9roler:)r*r:rJ fixed_roler=ZcuZ current_lvlZ threshold_lvlrrrrequiresJ           zBaseCork.requirec Cs|jjdkr!tdn||jjkrBtdnyt|Wntk rptdYnX||jj|<|jjdS)zCreate a new role. :param role: role name :type role: str. :param level: role level (0=lowest, 100=admin) :type level: int. :raises: AuthException on errors dz&The current user is not authorized to zThe role is already existingzThe level must be numeric.N) rIlevelrr)r rint ValueError save_roles)r*rJrNrrr create_roles  zBaseCork.create_rolecCsf|jjdkr!tdn||jjkrBtdn|jjj||jjdS)zyDeleta a role. :param role: role name :type role: str. :raises: AuthException on errors rMz&The current user is not authorized to zNonexistent role.N)rIrNrr)r rpoprQ)r*rJrrr delete_roles zBaseCork.delete_roleccs6x/t|jjD]}||jj|fVqWdS)zUList roles. :returns: (role, role_level) generator (sorted by role) N)sortedr)r )r*rJrrr list_rolesszBaseCork.list_rolescCs|std|jjdkr3tdn||jjkrTtdn||jjkrutdntt j }|j ||}|j d}i|d6|d6|d 6|d 6|d 6|d 6|jj|<|jj d S)aCreate a new user account. This method is available to users with level>=100 :param username: username :type username: str. :param role: role :type role: str. :param password: cleartext password :type password: str. :param email_addr: email address (optional) :type email_addr: str. :param description: description (free form) :type description: str. :raises: AuthException on errors zUsername must be provided.rMz3The current user is not authorized to create users.zUser is already existing.zNonexistent user role.r1rJr/ email_addrdesc creation_dater2N)AssertionErrorrIrNrr)rrr r6rr7_hashdecoder8)r*r:rJr;rW descriptiontstamphrrr create_users$zBaseCork.create_usercCsY|jjdkr!tdn||jjkrBtdn|j|jdS)zDelete a user account. This method is available to users with level>=100 :param username: username :type username: str. :raises: Exceptions on errors rMz&The current user is not authorized to zNonexistent user.N)rIrNrr)rruserrA)r*r:rrr delete_user@s zBaseCork.delete_userccsNxGt|jjD]3}|jj|}||d|d|dfVqWdS)z{List users. :return: (username, role, email_addr, description) generator (sorted by username) rJrWrXN)rUr)r)r*Zundrrr list_usersNszBaseCork.list_userscCs{|j}|jdd}|dkr6tdn|dk rg||jjkrgt||d|Std|dS)zCurrent autenticated user :returns: User() instance, if authenticated :raises: AuthException otherwise r:NzUnauthenticated userrFzUnknown user: %s)r@getrr)rUser)r*rFr:rrrrIXs  zBaseCork.current_userc CsSy|jd}Wntk r)dSYnX||jjkrOtd|ndS)zCheck if the current user is anonymous. :returns: True if the user is anonymous, False otherwise :raises: AuthException if the session username is unknown r:TzUnknown user: %sF)r@KeyErrorr)rr)r*r:rrruser_is_anonymousgs  zBaseCork.user_is_anonymouscCs/|dk r+||jjkr+t||SdS)z[Existing user :returns: User() instance if the user exist, None otherwise N)r)rrf)r*r:rrrraxs z BaseCork.userra2zSignup confirmationzviews/registration_email.tplc  Ksp|std|s$td|s6td||jjkrWtdn||jjkrxtdn|jj||krtdntjj} tt j } t j |d|d|d |d | d | | } |j j||| |j||} | jd } i|d6|d 6| d 6|d6|d6| d 6|jj| <|jjdS)a]Register a new user account. An email with a registration validation is sent to the user. WARNING: this method is available to unauthenticated users :param username: username :type username: str. :param password: cleartext password :type password: str. :param role: role (optional), defaults to 'user' :type role: str. :param max_level: maximum role level (optional), defaults to 50 :type max_level: int. :param email_addr: email address :type email_addr: str. :param subject: email subject :type subject: str. :param email_template: email template filename :type email_template: str. :param description: description (free form) :type description: str. :raises: AssertError or AAAException on errors zUsername must be provided.zA password must be provided.z"An email address must be provided.zUser is already existing.zNonexistent rolezUnauthorized roler:rWrJrYregistration_coder1r/rXN)rZr)rrr uuidZuuid4hexr6rr7bottletemplater$ send_emailr[r\pending_registrationsZsave_pending_registrations)r*r:r;rWrJZ max_levelsubjectemail_templater]kwargsrjrY email_textr_rrrr!s< zBaseCork.registerc Csy|jjj|}Wntk r9tdYnX|d}||jjkretdni|dd6|dd6|dd6|dd6|dd6ttj d 6|jj|<|jj d S) zValidate pending account registration, create a new account if successful. :param registration_code: registration code :type registration_code: str. zInvalid registration code.r:zUser is already existing.rJr/rWrXrYr2N) r)rprSrgrrrr6rr7r8)r*rjdatar:rrrvalidate_registrations        zBaseCork.validate_registrationzPassword reset confirmationzviews/password_reset_emailc KsA|sg|stdnx|jjjD]&\}}|d|kr.|}Pq.q.Wtdn||jjkrtdn|s|jj|jdd}|stdqn/|jj|d}||krtdn|j||} tj|d|d|d | |} |j j ||| dS) aEmail the user with a link to reset his/her password If only one parameter is passed, fetch the other from the users database. If both are passed they will be matched against the users database as a security check. :param username: username :type username: str. :param email_addr: email address :type email_addr: str. :param subject: email subject :type subject: str. :param email_template: email template filename :type email_template: str. :raises: AAAException on missing username or email_addr, AuthException on incorrect username/email_addr pair z6At least `username` or `email_addr` must be specified.rWzEmail address not found.zNonexistent user.NzEmail address not available.z&Username/email address pair not found.r: reset_code) rr)r iteritemsrer _reset_codermrnr$ro) r*r:rWrqrrrskvZstored_email_addrrwrtrrrsend_password_reset_emails4   z"BaseCork.send_password_reset_emailc Csfyt|j}|jdd\}}}}t|}t|tdsZtt|tdsutt|tds|jd}nWn$tt fk rt dYnXt ||j krt dnt|tdst|j |||s(t dn|j|}|dkrRtd n|jd |dS) aHValidate reset_code and update the account password The username is extracted from the reset_code token :param reset_code: reset token :type reset_code: str. :param password: new password :type password: str. :raises: AuthException for invalid reset tokens, AAAException :r zutf-8zInvalid reset code.zExpired reset code.NzNonexistent user.pwd)rr\splitrO isinstancetyperZr0 TypeErrorrPrr r%r4rarupdate)r*rwr;r:rWr^r_rarrrreset_passwords&   zBaseCork.reset_passwordcs(|||||fdd}|S)ah Create a decorator to be used for authentication and authorization :param username: A resource can be protected for a specific user :param role: Minimum role level required for authorization :param fixed_role: Only this role gets authorized :param fail_redirect: The URL to redirect to if a login is required. cs"fdd}|S)Ncs@ddl}|jfdd}|S)Nrc s/jdddd||S)Nr:rJrKr=)rL)aka)r=rKfuncrJsession_managerr:rrwrapperKs zVBaseCork.make_auth_decorator..auth_require..decorator..wrapper) functoolswraps)rrr)r=rKrJrr:)rr decoratorHs 0zEBaseCork.make_auth_decorator..auth_require..decoratorr)r:rJrKr=r)r)r=rKrJr:r auth_requireFs z2BaseCork.make_auth_decorator..auth_requirer)r*r:rJrKr=rr)rrmake_auth_decorator:s   zBaseCork.make_auth_decoratorcCs?|j}||d<|jdk r1|j|_n|jdS)z+Setup cookie for a user that just logged inr:N)r@r&domain _save_session)r*r:rFrrrr5Xs   zBaseCork._setup_cookiecCsp|dkr|j}n|dkr:|j||d|S|dkr\|j||d|Std|dS)zFHash username and password, generating salt value if required Nrsaltscryptz'Unknown hashing algorithm requested: %s)r( _hash_pbkdf2 _hash_scrypt RuntimeError)r*r:rrZalgorrrr[as    zBaseCork._hashcCststdn|dkr3tjd}nt|dksQtdd||f}tj||}d||}t|S)zHash username and password, generating salt value if required Use scrypt. :returns: base-64 encoded str. z8scrypt.hash required. Please install the scrypt library.N zIncorrect salt lengthz%s%ss) scrypt_availablerBosurandomlenrZrr/r)r:rr cleartextr_hashedrrrros zBaseCork._hash_scryptcCs|dkrtjd}nt|ts3tt|dksQtd|jd}t|tsut|jd}t|tst|d|}tjd||ddd}d ||}t |S) zHash username and password, generating salt value if required Use PBKDF2 from Beaker :returns: base-64 encoded str. NrzIncorrect salt lengthzutf-8ssha1 Zdklenp) rrrbytesrZrr0hashlib pbkdf2_hmacr)r:rrrr_rrrrrs zBaseCork._hash_pbkdf2cCst|tdstt|}|d}t|trOt|}n|dd}|dkr|j|||}||kS|dkr|j|||}||kStd|dS) zTVerity username/password pair against a salted hash :returns: bool rrr!psz%Unknown hashing algorithm in hash: %rN) rrrZrrOchrrrr)r*r:rr>decodedZ hash_typerr_rrrr4s      zBaseCork._verify_password`cCs|jjj}tr't|}nxk|D]c\}}tj|dd}tj}td|}|||kr.|jjj |q.q.WdS)z~Purge expired registration requests. :param exp_time: expiration time (hours) :type exp_time: float. rYz%Y-%m-%d %H:%M:%S.%fhoursN) r)rpitemsis_py3listrstrptimer7rrS)r*Zexp_timependingZ uuid_coderuZcreationnowZmaxdeltarrr_purge_expired_registrationss   z%BaseCork._purge_expired_registrationscCse|j||}dt}|jd}dj|jd|jd||f}t|S)zgenerate a reset_code token :param username: username :type username: str. :param email_addr: email address :type email_addr: str. :returns: Base-64 encoded token z%dzutf-8:)r[r r0joinr)r*r:rWr_trwrrrrys  zBaseCork._reset_code)rrrrr.r?rHrLrRrTrVr`rbrdpropertyrIrhrar!rvr|rrr5r[ staticmethodrrr4rryrrrrrCsN   ) N   &   = <   rc@s@eZdZdddZdddddZddZdS)rfNc Cs||_||jjjks*td||_|jjj|}|d|_|d|_|d|_|jjj|j|_ |dk ry+|d|_ |d|_ |d|_ WqYqXndS) aRepresent an authenticated user, exposing useful attributes: username, role, level, description, email_addr, session_creation_time, session_accessed_time, session_id. The session-related attributes are available for the current user only. :param username: username :type username: str. :param cork_obj: instance of :class:`Cork` z Unknown userrJrXrWNZ_creation_timeZ_accessed_time_id) _corkr)rrZr:rJr]rWr rNZsession_creation_timeZsession_accessed_timeZ session_id)r*r:Zcork_objrFZ user_datarrrr.s !       z User.__init__cCs|j}||jjjkr-tdn|dk rw||jjjkr]tdn||jjj|dsmtp|starttls|ssl) # Protocol name :// )? ( # Optional user:pass@ (?P[^:]*) # Match every char except ':' (: (?P.*) )? @ # Optional :pass )? (?P # Required FQDN on IP address ()| # Empty string ( # FQDN [a-zA-Z_\-] # First character cannot be a number [a-zA-Z0-9_\-\.]{,254} ) |( # IPv4 ([0-9]{1,3}\.){3} [0-9]{1,3} ) |( # IPv6 \[ # Square brackets ([0-9a-f]{,4}:){1,8} [0-9a-f]{,4} \] ) ) ( # Optional :port : (?P[0-9]{,5}) # Up to 5-digits port )? [/]? $ zSMTP URL seems incorrectprotoNsmtpportrizIncorrect SMTP port)rematchVERBOSEr groupdictrO)r*urlrrcrrrrVs     zMailer._parse_smtp_urlcCs |jdo|js%tdntd}||d<|j|d<||ds zMailer.join..)r)r*r)r*rrsz Mailer.joinc Cs'y|jWntk r"YnXdS)z@Class destructor: wait for threads to terminate within a timeoutN)rr)r*rrr__del__s zMailer.__del__N) rrrr.rrorrrrrrrr#Fs  5 " % r#)1base64rrrrZemail.mime.multipartrZemail.mime.textrloggingrZsmtplibr r threadingr r rmrrrsysrk version_infoprintexitrr ImportError basestring NameErrorr6ZbackendsrmajorrrrCrBrrobjectrrfrrrrr#rrrrsN      "        G bottle-cork-0.12.0/examples/cork/__pycache__/sqlalchemy_backend.cpython-34.pyc0000644000175000017500000001756712617446262027122 0ustar fedefede00000000000000 1L>V @sdZddlZddlmZddlmZeeZejj dkZ yPddl m Z m Z mZmZmZmZmZmZmZmZdZWnek rd ZYnXGd d d eZGd d d ejZGdddeZGdddejZdS)zJ .. module:: sqlalchemy_backend :synopsis: SQLAlchemy storage backend. N) getLogger) base_backend) create_enginedeleteselectColumn ForeignKeyIntegerMetaDataStringTableUnicodeTFc@s(eZdZddZddZdS) SqlRowProxycOs)tj|||||_||_dS)N)dict__init__sql_dictkey)selfrrargskwargsrH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyrs zSqlRowProxy.__init__cCs@tj||||jdk r<i||6|j|j1sz)SqlTable._row_to_value..)r#rkeys)rr*Zrow_keyZ row_valuer)r*rr _row_to_value.s  %zSqlTable._row_to_valuecCs1|jj}|jj|j}t|S)N)r!countr executeZscalarint)rqueryr"rrr__len__4szSqlTable.__len__cCs@t|jg|j|k}|jj|j}|dk S)N)rr#r r/fetchone)rrr1r*rrr __contains__9szSqlTable.__contains__cCs||kr6|}|jjj|j|k}n/i||jj6}|j||jj}|jj|j|dS)N) r!updatewherer#r'insertr r/values)rrrr8r1rrrr>s $ zSqlTable.__setitem__cCsbt|jg|j|k}|jj|j}|dkrQt|n|j|dS)Nr)rr!r#r r/r3KeyErrorr-)rrr1r*rrr __getitem__Js  zSqlTable.__getitem__ccsHt|jg}|jj|}x|D]}|d}|Vq+WdS)z#Iterate over table index key valuesrN)rr#r r/)rr1resultr*rrrr__iter__Qs   zSqlTable.__iter__ccsat|jg}|jj|}x6|D].}|d}|j|d}||fVq+WdS)zIterate over table rowsrrN)rr!r r/r-)rr1r;r*rdrrr iteritemsYs   zSqlTable.iteritemscCszt|jg|j|k}|jj|j}|dkrKtnt|j|j|k}|jj||S)N)rr!r#r r/r3r9r)rrr1r*rrrpopbs  z SqlTable.popcCs=|jj|}|jj|tjdt|dS)Nz %s inserted)r!r7r r/logdebugrepr)rr=r1rrrr7lszSqlTable.insertcCs0|jj}|jj|tjddS)Nz Table purged)r!rr r/r@info)rr1rrr empty_tableqszSqlTable.empty_tableN)rrr__doc__rr-r2r4rr:r<r>r?r7rDrrrrr&s        rc@s4eZdZddZddZddZdS)SqlSingleValueTablecCs#tj||||||_dS)N)rr _col_name)rr$r%r&Zcol_namerrrrxszSqlSingleValueTable.__init__cCs||j||jfS)N)r#rG)rr*rrrr-|sz!SqlSingleValueTable._row_to_valuecCs!tj||i||j6dS)N)rrrG)rrrrrrrszSqlSingleValueTable.__setitem__N)rrrrr-rrrrrrFws   rFc@sdeZdZddddddZddZd d Zd d Zd dZddZdS)SqlAlchemyBackendusersrolesregisterFc Ks0tstdnt|_|r|jdd\}}tra|jdratdnt|dd||_ y|j j d|Wn6t k r} zt j d | WYdd} ~ XnX|d kr|jd  r|j j d |qnt|dd||_ t||jtd tdddtdt|dtdtdddtdtdtdtdtdtdddtdtddd |_t||jtdtdddtdtdd|_t||jtdtdddtd tdddtdt|dtdtdddtdtdtdtdtdtddd |_t|j |jd |_t|j |jdd|_t|j |jd|_|r,|j|t jdndS)Nz(The SQLAlchemy library is not available./rZmysqlz-WARNING: MySQL is not supported under Python3encodingzutf-8zCREATE DATABASE %szFailed DB creation: %sz:memory:Z postgresqlzUSE %susernameZ primary_keyTrolez.rolehashZnullableF email_addrdesc creation_date last_loginlevelcodezTables created)sqlalchemy_available RuntimeErrorr _metadatarsplitis_py3 startswithprintrr r/ Exceptionr@rCrr rr r Z_usersr Z_rolesZ _pending_regrrIrFrJpending_registrations_initialize_storagerA) rZ db_full_urlZ users_tnameZ roles_tnameZpending_reg_tname initializerZdb_urldb_nameerrrrsP  $ !  ! zSqlAlchemyBackend.__init__cCs|jj|jdS)N)r[Z create_allr )rrdrrrrbsz%SqlAlchemyBackend._initialize_storagecCsQxJt|jjD]6}tjdt|j|jj|j qWdS)NzDropping table %s) reversedr[Z sorted_tablesr@rCrBr'r r/r)rr%rrr_drop_all_tablessz"SqlAlchemyBackend._drop_all_tablescCsdS)Nr)rrrr save_usersszSqlAlchemyBackend.save_userscCsdS)Nr)rrrr save_rolesszSqlAlchemyBackend.save_rolescCsdS)Nr)rrrrsave_pending_registrationssz,SqlAlchemyBackend.save_pending_registrationsN) rrrrrbrgrhrirjrrrrrHs ;    rH)rEsysloggingrrrr@ version_infomajorr]Z sqlalchemyrrrr r r r r rrrY ImportErrorrrrrFBackendrHrrrrs  F    Q bottle-cork-0.12.0/examples/cork/json_backend.pyc0000644000175000017500000001307512517133246021572 0ustar fedefede00000000000000 DR?R@(((sB/home/fede/newhome/projects/bottle-cork/tests/cork/json_backend.pyR)s     (RAtloggingRR'R<tsysR t ImportErrort simplejsont base_backendRt version_infotmajorR RtlogRRRtAttributeErrorRRtobjectR(((sB/home/fede/newhome/projects/bottle-cork/tests/cork/json_backend.pyts$       bottle-cork-0.12.0/examples/cork/json_backend.py0000644000175000017500000000000012517131104030266 1bottle-cork-0.12.0/cork/json_backend.pyustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/cork.py0000644000175000017500000000000012617465100025131 1bottle-cork-0.12.0/cork/cork.pyustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/sqlite_backend.py0000644000175000017500000000000012552271756031167 1bottle-cork-0.12.0/cork/sqlite_backend.pyustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/base_backend.pyc0000644000175000017500000000271412516462073021533 0ustar fedefede00000000000000 W*Uc@sUdZdefdYZdZdefdYZdefdYZdS( s4 .. module:: backend.py :synopsis: Base Backend. tBackendIOExceptioncBseZdZRS(sGeneric Backend I/O Exception(t__name__t __module__t__doc__(((sB/home/fede/newhome/projects/bottle-cork/tests/cork/base_backend.pyR scOs tdS(N(tNotImplementedError(targstkwargs((sB/home/fede/newhome/projects/bottle-cork/tests/cork/base_backend.pytnistBackendcBs eZdZeZeZeZRS(s7Base Backend class - to be subclassed by real backends.(RRRRt save_userst save_rolestsave_pending_registrations(((sB/home/fede/newhome/projects/bottle-cork/tests/cork/base_backend.pyRstTablecBs2eZdZeZeZeZeZeZeZ RS(s5Base Table class - to be subclassed by real backends.( RRRRt__len__t __contains__t __setitem__t __getitem__t__iter__t iteritems(((sB/home/fede/newhome/projects/bottle-cork/tests/cork/base_backend.pyR sN(Rt ExceptionRRtobjectRR (((sB/home/fede/newhome/projects/bottle-cork/tests/cork/base_backend.pyts bottle-cork-0.12.0/examples/cork/sqlalchemy_backend.pyc0000644000175000017500000002311312617446221022756 0ustar fedefede00000000000000 1L>Vc@sdZddlZddlmZddlmZeeZejj dkZ yPddl m Z m Z mZmZmZmZmZmZmZmZeZWnek reZnXdefd YZd ejfd YZd efd YZdejfdYZdS(sJ .. module:: sqlalchemy_backend :synopsis: SQLAlchemy storage backend. iN(t getLoggeri(t base_backendi( t create_enginetdeletetselecttColumnt ForeignKeytIntegertMetaDatatStringtTabletUnicodet SqlRowProxycBseZdZdZRS(cOs)tj|||||_||_dS(N(tdictt__init__tsql_dicttkey(tselfRRtargstkwargs((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyRs cCs@tj||||jdk r<i||6|j|j1s(RR tkeys(RR$trow_keyt row_value((R$RsH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt _row_to_value.s  "cCs1|jj}|jj|j}t|S(N(RtcountRtexecutetscalartint(RtqueryR((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt__len__4scCs@t|jg|j|k}|jj|j}|dk S(N(RRRR*tfetchoneR(RRR-R$((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt __contains__9scCs||kr6|}|jjj|j|k}n/i||jj6}|j||jj}|jj|j|dS(N( RtupdatetwhereRR!tinsertRR*tvalues(RRRR4R-((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyR>s $ cCsbt|jg|j|k}|jj|j}|dkrQt|n|j|dS(Ni( RRRRR*R/RtKeyErrorR((RRR-R$((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt __getitem__Js  ccsHt|jg}|jj|}x|D]}|d}|Vq+WdS(s#Iterate over table index key valuesiN(RRRR*(RR-tresultR$R((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt__iter__Qs   ccsat|jg}|jj|}x6|D].}|d}|j|d}||fVq+WdS(sIterate over table rowsiiN(RRRR*R((RR-R7R$Rtd((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt iteritemsYs   cCszt|jg|j|k}|jj|j}|dkrKtnt|j|j|k}|jj||S(N( RRRRR*R/RR5R(RRR-R$((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pytpopbs  cCs=|jj|}|jj|tjdt|dS(Ns %s inserted(RR3RR*tlogtdebugtrepr(RR9R-((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyR3lscCs0|jj}|jj|tjddS(Ns Table purged(RRRR*R<tinfo(RR-((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt empty_tableqs(RRt__doc__RR(R.R0RR6R8R:R;R3R@(((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyR&s       tSqlSingleValueTablecBs#eZdZdZdZRS(cCs#tj||||||_dS(N(RRt _col_name(RRRR tcol_name((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyRxscCs||j||jfS(N(RRC(RR$((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyR(|scCs!tj||i||j6dS(N(RRRC(RRR((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyRs(RRRR(R(((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyRBws  tSqlAlchemyBackendcBsJeZdddedZdZdZdZdZdZRS( tuserstrolestregisterc Kststdnt|_|r|jdd\}}tr\|jdr\dGHnt|dd||_y|jj d|Wn$t k r} t j d | nX|d kr|jd  r|jj d |qnt|dd||_t ||jtd tddttdt|dtdtddttdtdtdtdtdtddttdtddt |_t ||jtdtddttdtdt|_t ||jtdtddttd tddttdt|dtdtddttdtdtdtdtdtddt |_t|j|jd |_t|j|jdd|_t|j|jd|_|r|j|t jdndS(Ns(The SQLAlchemy library is not available.t/itmysqls-WARNING: MySQL is not supported under Python3tencodingsutf-8sCREATE DATABASE %ssFailed DB creation: %ss:memory:t postgresqlsUSE %stusernameit primary_keytroles.rolethashitnullablet email_addrtdesct creation_datet last_logintleveltcodesTables created(tsqlalchemy_availablet RuntimeErrorRt _metadatatrsplittis_py3t startswithRRR*t ExceptionR<R?R RR tTrueRR tFalset_usersRt_rolest _pending_regRRFRBRGtpending_registrationst_initialize_storageR=( Rt db_full_urlt users_tnamet roles_tnametpending_reg_tnamet initializeRtdb_urltdb_namete((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyRsP  !  ! cCs|jj|jdS(N(RZt create_allR(RRl((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyRescCsQxJt|jjD]6}tjdt|j|jj|j qWdS(NsDropping table %s( treversedRZt sorted_tablesR<R?R>R!RR*R(RR((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt_drop_all_tablesscCsdS(N((R((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt save_usersscCsdS(N((R((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyt save_rolesscCsdS(N((R((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pytsave_pending_registrationss( RRR`RReRqRrRsRt(((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyREs;    ( RAtsystloggingRtRRR<t version_infotmajorR\t sqlalchemyRRRRRRRR R R R_RXt ImportErrorR`R R RRBtBackendRE(((sH/home/fede/newhome/projects/bottle-cork/tests/cork/sqlalchemy_backend.pyts  F    Q bottle-cork-0.12.0/examples/cork/sqlalchemy_backend.py0000644000175000017500000000000012617446061032664 1bottle-cork-0.12.0/cork/sqlalchemy_backend.pyustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/sqlite_backend.pyc0000644000175000017500000002717212552272030022117 0ustar fedefede00000000000000 sUc@sdZddlmZddlmZeeZdefdYZdej fdYZ d e fd YZ d e fd YZ d e fdYZ de fdYZ dejfdYZdS(sB .. module:: sqlite_backend :synopsis: SQLite storage backend. i(t base_backendi(t getLoggert SqlRowProxycBseZdZdZRS(cCsJdt|jd|dD}tj||||_||_dS(Ncss'|]\\}}}||fVqdS(N((t.0tktktypetv((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pys si(tzipt_columnstdictt__init__t_tablet_key(tselfttabletkeytrowtli((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR s$ cCs'tj|||||j|jt|tst||j}t|||}||fS(N(t isinstancettupletAssertionErrorRR(R RRtrow_keyt row_value((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt _row_to_value's cCs-d|j}|jj|}|jdS(NsSELECT count() FROM %si(RRt run_querytfetchone(R tquerytret((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt__len__-s cCs5d|j|j|f}|jj|}|dk S(NsSELECT * FROM %s WHERE %s='%s'(RRRt fetch_onetNone(R RR+R((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt __contains__2sc CsPt|tstt|t|jd}}|| sVtt|||| swtt||t|t|jdkstdtt|tt|jdf|gg|jdD]}||^q}dj|j}djd|D}d|j||f} |jj | |} dS(sCreate or update a rowis%s %ss, css|] }dVqdS(t?N((Rtx((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pys Fss*INSERT OR REPLACE INTO %s (%s) VALUES (%s)N( R#R R%tsetRtreprtjoinRRtrun_query_using_conversion( R RRRtcnRt col_valuest col_namestquestion_marksR+R,((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR9s !!%/+cCsZd|j|j|f}|jj|}|dkrFt|n|j||dS(NsSELECT * FROM %s WHERE %s='%s'i(RRRR.R/tKeyErrorR((R RR+R((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt __getitem__Ms  ccsFd|j|jf}|jj|}x|D]}|dVq/WdS(s#Iterate over table index key valuessSELECT %s FROM %siN(RRRR)(R R+tresultR((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt__iter__Ws ccsjd|j}|jj|}xD|D]<}tt|j|}|j|j|j|fVq&WdS(sIterate over table rowssSELECT * FROM %sN(RRR)R RRtpopR(R R+R=Rtd((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt iteritems^s   cCs<|j|}d|j|j|f}|jj||S(NsDELETE FROM %s WHERE %s='%s'(R<RRRR.(R RR@R+((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR?hs cCs tdS(N(tNotImplementedError(R R@((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pytinsertpscCs tdS(N(RB(R ((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt empty_tablesscCsg}xy|jD]n\}}|tkr1d}n|tkrFd}n||jkr^d}nd}|jd|||fqWdj|}d|j|f}|jj|dS( sIssue table creationtINTEGERtTEXTsPRIMARY KEY ASCts%s %s %st,sCREATE TABLE %s (%s)N( RtinttstrRtappendR5RRR)(R tcctcol_nametcol_typetextrasR+((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt create_tablevs     (RRt__doc__R R(R-R0RR<R>RAR?RCRDRP(((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyRs        tSingleValueTablecBs#eZdZdZdZRS(cGs*tt|j||jd|_dS(Ni(tsuperRRR Rt _value_col(R targs((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR scCsWt|t std|j|j|jf}||f}|jj||}dS(sCreate or update a rows0INSERT OR REPLACE INTO %s (%s, %s) VALUES (?, ?)N(R#R R%RRRTRR6(R RRR+R8R,((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyRs  cCsTd|j|j|j|f}|jj|}|dkrLt|n|dS(NsSELECT %s FROM %s WHERE %s='%s'i(RTRRRR.R/R;(R RR+R((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR<s  (RRR RR<(((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyRRs  t UsersTablecBseZdZRS(cOsedtfdtfdtfdtfdtfdtfdtff|_tt|j||dS(Ntusernametrolethasht email_addrtdesct creation_datet last_login(RJRRSRVR (R RUtkwargs((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR s      (RRR (((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyRVst RolesTablecBseZdZRS(cOs8dtfdtff|_tt|j||dS(NRXtlevel(RJRIRRSR_R (R RUR^((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR s (RRR (((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR_stPendingRegistrationsTablecBseZdZRS(cOsedtfdtfdtfdtfdtfdtfdtff|_tt|j||dS(NtcodeRWRXRYRZR[R\(RJRRSRaR (R RUR^((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR s      (RRR (((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyRast SQLiteBackendcBsteZdddedZedZdZdZdZdZ d Z d Z d Z d Z RS( tuserstrolestregistercCs||_t|||_t|||_t|||_|r||jj|jj|jjtj dndS(NsTables created( t _filenameRVRdR_ReRatpending_registrationsRPtlogtdebug(R tfilenamet users_tnamet roles_tnametpending_reg_tnamet initialize((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR s    cCsNy |jSWn<tk rIddl}|j|jdd|_|jSXdS(Nitisolation_level(t _connectiontAttributeErrortsqlite3tconnectRgR/(R Rs((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyRs    cCs|jj|S(N(Rqtexecute(R R+((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR)scCs|jj||S(N(RqRu(R R+RU((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR6scCs|jj|jS(N(RqRuR*(R R+((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyR.scCs tdS(N(RB(R tdb_name((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt_initialize_storagescCs tdS(N(RB(R ((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt_drop_all_tablesscCsdS(N((R ((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt save_usersscCsdS(N((R ((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyt save_rolesscCsdS(N((R ((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pytsave_pending_registrationss(RRtFalseR tpropertyRR)R6R.RwRxRyRzR{(((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyRcs       N(RQRGRtloggingRRRiR RRRRRVR_RatBackendRc(((sD/home/fede/newhome/projects/bottle-cork/tests/cork/sqlite_backend.pyts  p bottle-cork-0.12.0/examples/cork/mongodb_backend.py.broken0000644000175000017500000001262612517527122023363 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt """ .. module:: mongodb_backend :synopsis: MongoDB storage backend. """ from logging import getLogger log = getLogger(__name__) from .base_backend import Backend, Table try: import pymongo is_pymongo_2 = (pymongo.version_tuple[0] == 2) except ImportError: # pragma: no cover pass class MongoTable(Table): """Abstract MongoDB Table. Allow dictionary-like access. """ def __init__(self, name, key_name, collection): self._name = name self._key_name = key_name self._coll = collection def create_index(self): """Create collection index.""" self._coll.create_index( self._key_name, drop_dups=True, unique=True, ) def __len__(self): return self._coll.count() def __contains__(self, value): r = self._coll.find_one({self._key_name: value}) return r is not None def __iter__(self): """Iter on dictionary keys""" if is_pymongo_2: r = self._coll.find(fields=[self._key_name,]) else: r = self._coll.find(projection=[self._key_name,]) return (i[self._key_name] for i in r) def iteritems(self): """Iter on dictionary items. :returns: generator of (key, value) tuples """ r = self._coll.find() for i in r: d = i.copy() d.pop(self._key_name) d.pop('_id') yield (i[self._key_name], d) def pop(self, key_val): """Remove a dictionary item""" r = self[key_val] self._coll.remove({self._key_name: key_val}, w=1) return r class MongoSingleValueTable(MongoTable): """MongoDB table accessible as a simple key -> value dictionary. Used to store roles. """ # Values are stored in a MongoDB "column" named "val" def __init__(self, *args, **kw): super(MongoSingleValueTable, self).__init__(*args, **kw) def __setitem__(self, key_val, data): assert not isinstance(data, dict) spec = {self._key_name: key_val} data = {self._key_name: key_val, 'val': data} if is_pymongo_2: self._coll.update(spec, data, upsert=True, w=1) else: self._coll.update(spec, {'$set': data}, upsert=True, w=1) def __getitem__(self, key_val): r = self._coll.find_one({self._key_name: key_val}) if r is None: raise KeyError(key_val) return r['val'] class MongoMutableDict(dict): """Represent an item from a Table. Acts as a dictionary. """ def __init__(self, parent, root_key, d): """Create a MongoMutableDict instance. :param parent: Table instance :type parent: :class:`MongoTable` """ super(MongoMutableDict, self).__init__(d) self._parent = parent self._root_key = root_key def __setitem__(self, k, v): super(MongoMutableDict, self).__setitem__(k, v) self._parent[self._root_key] = self return spec = {self._parent._key_name: self._root_key} if is_pymongo_2: r = self._parent._coll.update(spec, {k: v}, upsert=True) else: r = self._parent._coll.update_one(spec, {'$set': {k: v}}, upsert=True) class MongoMultiValueTable(MongoTable): """MongoDB table accessible as a dictionary. """ def __init__(self, *args, **kw): super(MongoMultiValueTable, self).__init__(*args, **kw) def __setitem__(self, key_val, data): assert isinstance(data, dict) key_name = self._key_name if key_name in data: assert data[key_name] == key_val else: data[key_name] = key_val spec = {key_name: key_val} if is_pymongo_2: self._coll.update(spec, data, upsert=True, w=1) else: print("Spec %r" % spec) print("data %r" % data) self._coll.update(spec, {'$set': data}, upsert=True, w=1) #r = self._coll.update_one(spec, {'$set': data}, upsert=True) def __getitem__(self, key_val): r = self._coll.find_one({self._key_name: key_val}) if r is None: raise KeyError(key_val) return MongoMutableDict(self, key_val, r) class MongoDBBackend(Backend): def __init__(self, db_name='cork', hostname='localhost', port=27017, initialize=False, username=None, password=None): """Initialize MongoDB Backend""" connection = pymongo.MongoClient(host=hostname, port=port) db = connection[db_name] if username and password: db.authenticate(username, password) self.users = MongoMultiValueTable('users', 'login', db.users) self.pending_registrations = MongoMultiValueTable( 'pending_registrations', 'pending_registration', db.pending_registrations ) self.roles = MongoSingleValueTable('roles', 'role', db.roles) if initialize: self._initialize_storage() def _initialize_storage(self): """Create MongoDB indexes.""" for c in (self.users, self.roles, self.pending_registrations): c.create_index() def save_users(self): pass def save_roles(self): pass def save_pending_registrations(self): pass bottle-cork-0.12.0/examples/cork/__init__.py0000644000175000017500000000000012617462665026550 1bottle-cork-0.12.0/cork/__init__.pyustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/backends.py0000644000175000017500000000000012517131104026552 1bottle-cork-0.12.0/cork/backends.pyustar fedefede00000000000000bottle-cork-0.12.0/examples/cork/backends.pyc0000644000175000017500000000103412517133246020714 0ustar fedefede00000000000000 D/home/fede/newhome/projects/bottle-cork/tests/cork/backends.pytsbottle-cork-0.12.0/examples/cork/cork.pyc0000644000175000017500000010040212617465277020114 0ustar fedefede00000000000000 @j>Vc@sKddlmZmZddlmZmZddlmZddlmZddl m Z ddl m Z m Z ddlmZddlmZdd lZdd lZdd lZdd lZdd lZdd lZejd d !d d dfkrdGHejdnydd lZeZWnek r=eZnXyeWnek r_e ZnXddl!m"Z"ejj#d kZ$e e%Z&de'fdYZ(de(fdYZ)de*fdYZ+de*fdYZ,de'fdYZ-dZ.de+fdYZ/de+fd YZ0d!e*fd"YZ1d S(#i(t b64encodet b64decode(tdatetimet timedelta(t MIMEMultipart(tMIMEText(t getLogger(tSMTPtSMTP_SSL(tThread(ttimeNiiiiisPython >= 2.7.8 is requiredi(t JsonBackendt AAAExceptioncBseZdZRS(s.Generic Authentication/Authorization Exception(t__name__t __module__t__doc__(((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR 9st AuthExceptioncBseZdZRS(s:Authentication Exception: incorrect username/password pair(R RR(((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR>stBaseCorkc BsseZdZd#d#d#ed#d#dd#dZd#d#dZdddZd#d#ed#dZdZ dZ d Z d#d#d Z d Z d Zed ZedZdZddddd#dZdZd#d#dddZdZd#d#eddZdZd#d#dZed#dZed#dZdZd d!Zd"ZRS($sAbstract classt localhostc Cs|r|}nt|||_d |_||_|p<d|_d|_|d krt|ddddd d d ||_n ||_d S(s]Auth/Authorization/Accounting class :param directory: configuration directory :type directory: str. :param users_fname: users filename (without .json), defaults to 'users' :type users_fname: str. :param roles_fname: roles filename (without .json), defaults to 'roles' :type roles_fname: str. iisbeaker.sessiontPBKDF2t users_fnametuserst roles_fnametrolestpending_reg_fnametregistert initializeNiQ( tMailertmailertpassword_reset_timeouttsession_domaintsession_key_nametpreferred_hashing_algorithmtNoneR t_store( tselft directorytbackendt email_senderRRt smtp_servertsmtp_urlR((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt__init__Fs        cCs||jjkr|jj|d}t|drG|jd}n|j|||}|r|j|ttj|jj|d<|jj |r|j |nt Sn|r|j |nt S(s'Check login credentials for an existing user. Optionally redirect the user to another page (typically /login) :param username: username :type username: str or unicode. :param password: cleartext password :type password: str.or unicode :param success_redirect: redirect authorized users (optional) :type success_redirect: str. :param fail_redirect: redirect unauthorized users (optional) :type fail_redirect: str. :returns: True for successful logins, else False thashtencodetasciit last_login( R"RthasattrR+t_verify_passwordt _setup_cookietstrRtutcnowt save_userst _redirecttTruetFalse(R#tusernametpasswordtsuccess_redirectt fail_redirectt salted_hasht authenticated((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytlogines&   s/logincCsby|j}|jWn7tk rP}tjdt||j|nX|j|dS(sLog the user out, remove cookie :param success_redirect: redirect the user after logging out :type success_redirect: str. :param fail_redirect: redirect the user if it is not logged in :type fail_redirect: str. sException %s while logging out.N(t_beaker_sessiontdeletet ExceptiontlogtdebugtreprR4(R#R9R:tsessionte((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytlogouts cCs|dk r0||jjkr0tdq0n|rQ|dkrQtdn|dk r~||jjkr~tdny |j}Wn9tk r|dkrtdq|j|nX|j|jjkrtdn|dk r8||jj kr dS|dkr(tdn|j|n|r||jjkrTdS|dkrotdn|j|n|dk r|jj|jj}|jj|}||krdS|dkrtd n|j|ndS( sEnsure the user is logged in has the required role (or higher). Optionally redirect the user to another page (typically /login) If both `username` and `role` are specified, both conditions need to be satisfied. If none is specified, any authenticated user will be authorized. By default, any role with higher level than `role` will be authorized; set fixed_role=True to prevent this. :param username: username (optional) :type username: str. :param role: role :type role: str. :param fixed_role: require user role to match `role` strictly :type fixed_role: bool. :param redirect: redirect unauthorized users (optional) :type redirect: str. sNonexistent users3A role must be specified if fixed_role has been setsRole not foundsUnauthenticated users#Role not found for the current userNs'Unauthorized access: incorrect usernames#Unauthorized access: incorrect rolesUnauthorized access: ( R!R"RR Rt current_userRR4troleR7(R#R7RHt fixed_roleR:tcut current_lvlt threshold_lvl((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytrequiresJ           cCs|jjdkr!tdn||jjkrBtdnyt|Wntk rotdnX||jj|<|jjdS(sCreate a new role. :param role: role name :type role: str. :param level: role level (0=lowest, 100=admin) :type level: int. :raises: AuthException on errors ids&The current user is not authorized to sThe role is already existingsThe level must be numeric.N( RGtlevelRR"RR tintt ValueErrort save_roles(R#RHRN((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt create_roles  cCsf|jjdkr!tdn||jjkrBtdn|jjj||jjdS(syDeleta a role. :param role: role name :type role: str. :raises: AuthException on errors ids&The current user is not authorized to sNonexistent role.N(RGRNRR"RR tpopRQ(R#RH((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt delete_roles ccs6x/t|jjD]}||jj|fVqWdS(sUList roles. :returns: (role, role_level) generator (sorted by role) N(tsortedR"R(R#RH((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt list_rolesscCs|std|jjdkr3tdn||jjkrTtdn||jjkrutdntt j }|j ||}|j d}i|d6|d6|d 6|d 6|d 6|d 6|jj|<|jj d S(sCreate a new user account. This method is available to users with level>=100 :param username: username :type username: str. :param role: role :type role: str. :param password: cleartext password :type password: str. :param email_addr: email address (optional) :type email_addr: str. :param description: description (free form) :type description: str. :raises: AuthException on errors sUsername must be provided.ids3The current user is not authorized to create users.sUser is already existing.sNonexistent user role.R,RHR*t email_addrtdesct creation_dateR-N(tAssertionErrorRGRNRR"RR RR1RR2t_hashtdecodeR3(R#R7RHR8RWt descriptionttstampth((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt create_users$cCsY|jjdkr!tdn||jjkrBtdn|j|jdS(sDelete a user account. This method is available to users with level>=100 :param username: username :type username: str. :raises: Exceptions on errors ids&The current user is not authorized to sNonexistent user.N(RGRNRR"RR tuserR?(R#R7((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt delete_user@s ccsNxGt|jjD]3}|jj|}||d|d|dfVqWdS(s{List users. :return: (username, role, email_addr, description) generator (sorted by username) RHRWRXN(RUR"R(R#tuntd((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt list_usersNscCs{|j}|jdd}|dkr6tdn|dk rg||jjkrgt||d|Std|dS(sCurrent autenticated user :returns: User() instance, if authenticated :raises: AuthException otherwise R7sUnauthenticated userRDsUnknown user: %sN(R>tgetR!RR"RtUser(R#RDR7((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyRGXs  cCsOy|jd}Wntk r%tSX||jjkrKtd|ntS(sCheck if the current user is anonymous. :returns: True if the user is anonymous, False otherwise :raises: AuthException if the session username is unknown R7sUnknown user: %s(R>tKeyErrorR5R"RRR6(R#R7((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytuser_is_anonymousgs cCs/|dk r+||jjkr+t||SdS(s[Existing user :returns: User() instance if the user exist, None otherwise N(R!R"RRg(R#R7((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyRaxs Rai2sSignup confirmationsviews/registration_email.tplc  Ksp|std|s$td|s6td||jjkrWtdn||jjkrxtdn|jj||krtdntjj} tt j } t j |d|d|d |d | d | | } |j j||| |j||} | jd } i|d6|d 6| d 6|d6|d6| d 6|jj| <|jjdS(s]Register a new user account. An email with a registration validation is sent to the user. WARNING: this method is available to unauthenticated users :param username: username :type username: str. :param password: cleartext password :type password: str. :param role: role (optional), defaults to 'user' :type role: str. :param max_level: maximum role level (optional), defaults to 50 :type max_level: int. :param email_addr: email address :type email_addr: str. :param subject: email subject :type subject: str. :param email_template: email template filename :type email_template: str. :param description: description (free form) :type description: str. :raises: AssertError or AAAException on errors sUsername must be provided.sA password must be provided.s"An email address must be provided.sUser is already existing.sNonexistent rolesUnauthorized roleR7RWRHRYtregistration_codeR,R*RXN(RZR"RR Rtuuidtuuid4thexR1RR2tbottlettemplateRt send_emailR[R\tpending_registrationstsave_pending_registrations(R#R7R8RWRHt max_leveltsubjecttemail_templateR]tkwargsRjRYt email_textR_((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyRs< cCsy|jjj|}Wntk r8tdnX|d}||jjkrdtdni|dd6|dd6|dd6|dd6|dd6ttj d 6|jj|<|jj d S( sValidate pending account registration, create a new account if successful. :param registration_code: registration code :type registration_code: str. sInvalid registration code.R7sUser is already existing.RHR*RWRXRYR-N( R"RqRSRhRRR R1RR2R3(R#RjtdataR7((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytvalidate_registrations        sPassword reset confirmationsviews/password_reset_emailc KsA|sg|stdnx|jjjD]&\}}|d|kr.|}Pq.q.Wtdn||jjkrtdn|s|jj|jdd }|stdqn/|jj|d}||krtdn|j||} tj |d|d|d| |} |j j ||| d S( sEmail the user with a link to reset his/her password If only one parameter is passed, fetch the other from the users database. If both are passed they will be matched against the users database as a security check. :param username: username :type username: str. :param email_addr: email address :type email_addr: str. :param subject: email subject :type subject: str. :param email_template: email template filename :type email_template: str. :raises: AAAException on missing username or email_addr, AuthException on incorrect username/email_addr pair s6At least `username` or `email_addr` must be specified.RWsEmail address not found.sNonexistent user.sEmail address not available.s&Username/email address pair not found.R7t reset_codeN( R R"Rt iteritemsRfR!Rt _reset_codeRnRoRRp( R#R7RWRtRuRvtktvtstored_email_addrRzRw((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytsend_password_reset_emails4   cCseyt|j}|jdd\}}}}t|}t|tdsZtt|tdsutt|tds|jd}nWn#tt fk rt dnXt ||j krt dnt|tdst|j |||s't dn|j|}|d krQtdn|jd |d S( sHValidate reset_code and update the account password The username is extracted from the reset_code token :param reset_code: reset token :type reset_code: str. :param password: new password :type password: str. :raises: AuthException for invalid reset tokens, AAAException t:iutsutf-8sInvalid reset code.sExpired reset code.sNonexistent user.tpwdN(RR\tsplitROt isinstancettypeRZR+t TypeErrorRPRR RR/RaR!R tupdate(R#RzR8R7RWR^R_Ra((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytreset_passwords&   cs%|||||fd}|S(sh Create a decorator to be used for authentication and authorization :param username: A resource can be protected for a specific user :param role: Minimum role level required for authorization :param fixed_role: Only this role gets authorized :param fail_redirect: The URL to redirect to if a login is required. csfd}|S(Ncs=ddl}|jfd}|S(Nic s/jdddd||S(NR7RHRIR:(RM(tatka(R:RItfuncRHtsession_managerR7(s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytwrapperKs (t functoolstwraps(RRR(R:RIRHRR7(Rs:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt decoratorHs -((R7RHRIR:R(R(R:RIRHR7s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt auth_requireFs ((R#R7RHRIR:R((Rs:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pytmake_auth_decorator:s   cCs?|j}||d<|jdk r1|j|_n|jdS(s+Setup cookie for a user that just logged inR7N(R>RR!tdomaint _save_session(R#R7RD((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR0Xs   cCsp|dkr|j}n|dkr:|j||d|S|dkr\|j||d|Std|dS(sFHash username and password, generating salt value if required Rtsalttscrypts'Unknown hashing algorithm requested: %sN(R!R t _hash_pbkdf2t _hash_scryptt RuntimeError(R#R7RRtalgo((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR[as    cCststdn|dkr3tjd}nt|dksQtdd||f}tj||}d||}t |S(sHash username and password, generating salt value if required Use scrypt. :returns: base-64 encoded str. s8scrypt.hash required. Please install the scrypt library.i sIncorrect salt lengths%s%stsN( tscrypt_availableR@R!tosturandomtlenRZRR*R(R7RRt cleartextR_thashed((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyRos cCs|d krtjd}nt|ts3tt|dksQtd|jd}t|tsut|jd}t|tst|d|}tj d||ddd}d||}t |S( sHash username and password, generating salt value if required Use PBKDF2 from Beaker :returns: base-64 encoded str. i sIncorrect salt lengthsutf-8ttsha1i tdklentpN( R!RRRtbytesRZRR+thashlibt pbkdf2_hmacR(R7RRRR_R((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyRs cCst|tdstt|}|d}t|trOt|}n|dd!}|dkr|j|||}||kS|dkr|j|||}||kStd|dS( sTVerity username/password pair against a salted hash :returns: bool Riii!RRs%Unknown hashing algorithm in hash: %rN( RRRZRROtchrRRR(R#R7RR;tdecodedt hash_typeRR_((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR/s       i`cCs|jjj}tr't|}nxk|D]c\}}tj|dd}tj}td|}|||kr.|jjj |q.q.WdS(s~Purge expired registration requests. :param exp_time: expiration time (hours) :type exp_time: float. RYs%Y-%m-%d %H:%M:%S.%fthoursN( R"Rqtitemstis_py3tlistRtstrptimeR2RRS(R#texp_timetpendingt uuid_codeRxtcreationtnowtmaxdelta((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyt_purge_expired_registrationss   cCse|j||}dt}|jd}dj|jd|jd||f}t|S(sgenerate a reset_code token :param username: username :type username: str. :param email_addr: email address :type email_addr: str. :returns: Base-64 encoded token s%dsutf-8R(R[R R+tjoinR(R#R7RWR_ttRz((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR|s  N( R RRR!R6R)R=RFRMRRRTRVR`RbRetpropertyRGRiRaRRyRRRR0R[t staticmethodRRR/RR|(((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyRCsN   )  N    &    =  <     RgcBs/eZddZddddZdZRS(cCs||_||jjjks*td||_|jjj|}|d|_|d|_|d|_|jjj|j|_ |dk ry+|d|_ |d|_ |d|_ WqqXndS( sRepresent an authenticated user, exposing useful attributes: username, role, level, description, email_addr, session_creation_time, session_accessed_time, session_id. The session-related attributes are available for the current user only. :param username: username :type username: str. :param cork_obj: instance of :class:`Cork` s Unknown userRHRXRWt_creation_timet_accessed_timet_idN(t_corkR"RRZR7RHR]RWRRNR!tsession_creation_timetsession_accessed_timet session_id(R#R7tcork_objRDt user_data((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR)s !       cCs|j}||jjjkr-tdn|dk rw||jjjkr]tdn||jjj|d.scCs|jjdS(N(R>tsave(R#((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR3s(R RRR4RR>R(((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR)st FlaskCorkcBs/eZedZedZdZRS(cCst|dS(N(R(R((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR48scCsddl}|jS(s Get sessioniN(tflaskRD(R#R((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR><s cCsdS(N((R#((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyRBs(R RRR4RR>R(((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR7sRcBsDeZdedZdZdZdZdZdZRS(icCs:||_||_||_g|_|j||_dS(sSend emails asyncronously :param sender: Sender email address :type sender: str. :param smtp_server: SMTP server :type smtp_server: str. N(tsendert join_timeoutt use_threadst_threadst_parse_smtp_urlt_conf(R#RR(RR((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyR)Hs     cCstjd|tj}|s-tdn|j}|dd krVd|dsmtp|starttls|ssl) # Protocol name :// )? ( # Optional user:pass@ (?P[^:]*) # Match every char except ':' (: (?P.*) )? @ # Optional :pass )? (?P # Required FQDN on IP address ()| # Empty string ( # FQDN [a-zA-Z_\-] # First character cannot be a number [a-zA-Z0-9_\-\.]{,254} ) |( # IPv4 ([0-9]{1,3}\.){3} [0-9]{1,3} ) |( # IPv6 \[ # Square brackets ([0-9a-f]{,4}:){1,8} [0-9a-f]{,4} \] ) ) ( # Optional :port : (?P[0-9]{,5}) # Up to 5-digits port )? [/]? $ sSMTP URL seems incorrecttprototsmtptportiiisIncorrect SMTP portN(tretmatchtVERBOSERt groupdictR!RO(R#turlRRd((s:/home/fede/newhome/projects/bottle-cork/tests/cork/cork.pyRVs     cCs |jdo|js%tdntd}||d<|j|d<||dsN             G bottle-cork-0.12.0/examples/cork/mongodb_backend.py.works0000644000175000017500000001177612517526136023261 0ustar fedefede00000000000000# Cork - Authentication module for the Bottle web framework # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt """ .. module:: mongodb_backend :synopsis: MongoDB storage backend. """ from logging import getLogger log = getLogger(__name__) from .base_backend import Backend, Table try: import pymongo is_pymongo_2 = (pymongo.version_tuple[0] == 2) except ImportError: # pragma: no cover pass class MongoTable(Table): """Abstract MongoDB Table. Allow dictionary-like access. """ def __init__(self, name, key_name, collection): self._name = name self._key_name = key_name self._coll = collection def create_index(self): """Create collection index.""" self._coll.create_index( self._key_name, drop_dups=True, unique=True, ) def __len__(self): return self._coll.count() def __contains__(self, value): r = self._coll.find_one({self._key_name: value}) return r is not None def __iter__(self): """Iter on dictionary keys""" if is_pymongo_2: r = self._coll.find(fields=[self._key_name,]) else: r = self._coll.find(projection=[self._key_name,]) return (i[self._key_name] for i in r) def iteritems(self): """Iter on dictionary items. :returns: generator of (key, value) tuples """ r = self._coll.find() for i in r: d = i.copy() d.pop(self._key_name) d.pop('_id') yield (i[self._key_name], d) def pop(self, key_val): """Remove a dictionary item""" r = self[key_val] self._coll.remove({self._key_name: key_val}, w=1) return r class MongoSingleValueTable(MongoTable): """MongoDB table accessible as a simple key -> value dictionary. Used to store roles. """ # Values are stored in a MongoDB "column" named "val" def __init__(self, *args, **kw): super(MongoSingleValueTable, self).__init__(*args, **kw) def __setitem__(self, key_val, data): assert not isinstance(data, dict) spec = {self._key_name: key_val} data = {self._key_name: key_val, 'val': data} if is_pymongo_2: self._coll.update(spec, data, upsert=True, w=1) else: self._coll.update(spec, {'$set': data}, upsert=True, w=1) def __getitem__(self, key_val): r = self._coll.find_one({self._key_name: key_val}) if r is None: raise KeyError(key_val) return r['val'] class MongoMutableDict(dict): """Represent an item from a Table. Acts as a dictionary. """ def __init__(self, parent, root_key, d): """Create a MongoMutableDict instance. :param parent: Table instance :type parent: :class:`MongoTable` """ super(MongoMutableDict, self).__init__(d) self._parent = parent self._root_key = root_key def __setitem__(self, k, v): super(MongoMutableDict, self).__setitem__(k, v) self._parent[self._root_key] = self class MongoMultiValueTable(MongoTable): """MongoDB table accessible as a dictionary. """ def __init__(self, *args, **kw): super(MongoMultiValueTable, self).__init__(*args, **kw) def __setitem__(self, key_val, data): assert isinstance(data, dict) key_name = self._key_name if key_name in data: assert data[key_name] == key_val else: data[key_name] = key_val spec = {key_name: key_val} if is_pymongo_2: self._coll.update(spec, data, upsert=True, w=1) else: self._coll.update(spec, {'$set': data}, upsert=True, w=1) def __getitem__(self, key_val): r = self._coll.find_one({self._key_name: key_val}) if r is None: raise KeyError(key_val) return MongoMutableDict(self, key_val, r) class MongoDBBackend(Backend): def __init__(self, db_name='cork', hostname='localhost', port=27017, initialize=False, username=None, password=None): """Initialize MongoDB Backend""" connection = pymongo.MongoClient(host=hostname, port=port) db = connection[db_name] if username and password: db.authenticate(username, password) self.users = MongoMultiValueTable('users', 'login', db.users) self.pending_registrations = MongoMultiValueTable( 'pending_registrations', 'pending_registration', db.pending_registrations ) self.roles = MongoSingleValueTable('roles', 'role', db.roles) if initialize: self._initialize_storage() def _initialize_storage(self): """Create MongoDB indexes.""" for c in (self.users, self.roles, self.pending_registrations): c.create_index() def save_users(self): pass def save_roles(self): pass def save_pending_registrations(self): pass bottle-cork-0.12.0/examples/cork/mongodb_backend.pyc0000644000175000017500000002016112520652653022242 0ustar fedefede00000000000000 Y@Uc@sdZddlmZeeZddlmZmZy#ddlZej ddkZ Wne k rnnXdefd YZ d e fd YZ d efd YZde fdYZdefdYZdS(sD .. module:: mongodb_backend :synopsis: MongoDB storage backend. i(t getLoggeri(tBackendtTableNiit MongoTablecBsMeZdZdZdZdZdZdZdZdZ RS(s>Abstract MongoDB Table. Allow dictionary-like access. cCs||_||_||_dS(N(t_namet _key_namet_coll(tselftnametkey_namet collection((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyt__init__s  cCs#|jj|jdtdtdS(sCreate collection index.t drop_dupstuniqueN(Rt create_indexRtTrue(R((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyRs  cCs |jjS(N(Rtcount(R((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyt__len__&scCs&|jji||j6}|dk S(N(Rtfind_oneRtNone(Rtvaluetr((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyt __contains__)scsStr$jjdjg}njjdjg}fd|DS(sIter on dictionary keystfieldst projectionc3s|]}|jVqdS(N(R(t.0ti(R(s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pys 4s(t is_pymongo_2RtfindR(RR((Rs?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyt__iter__-sccs_|jj}xI|D]A}|j}|j|j|jd||j|fVqWdS(sVIter on dictionary items. :returns: generator of (key, value) tuples t_idN(RRtcopytpopR(RRRtd((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyt iteritems6s    cCs.||}|jji||j6dd|S(sRemove a dictionary itemtwi(RtremoveR(Rtkey_valR((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyR Bs  ( t__name__t __module__t__doc__R RRRRR"R (((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyRs     tMongoSingleValueTablecBs)eZdZdZdZdZRS(s[MongoDB table accessible as a simple key -> value dictionary. Used to store roles. cOstt|j||dS(N(tsuperR)R (Rtargstkw((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyR NscCst|t sti||j6}i||j6|d6}trl|jj|i|d6dtddn |jj|i|d6dtdS(Ntvals$settupsertR#i( t isinstancetdicttAssertionErrorRRRtupdateRt update_one(RR%tdatatspec((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyt __setitem__Qs )cCs?|jji||j6}|dkr7t|n|dS(NR-(RRRRtKeyError(RR%R((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyt __getitem__Zs (R&R'R(R R6R8(((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyR)Is  tMongoMutableDictcBs eZdZdZdZRS(s:Represent an item from a Table. Acts as a dictionary. cCs,tt|j|||_||_dS(s|Create a MongoMutableDict instance. :param parent: Table instance :type parent: :class:`MongoTable` N(R*R9R t_parentt _root_key(Rtparenttroot_keyR!((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyR ds cCstt|j||i|j|jj6}trd|jjj|ii||6d6dt }n,|jjj |ii||6d6dt }dS(Ns$setR.( R*R9R6R;R:RRRR2RR3(RtktvR5R((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyR6ms /(R&R'R(R R6(((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyR9as tMongoMultiValueTablecBs)eZdZdZdZdZRS(s.MongoDB table accessible as a dictionary. cOstt|j||dS(N(R*R@R (RR+R,((s?/home/fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyR zscCst|tst|j}||krC|||ksMtn |||s   4bottle-cork-0.12.0/examples/cork/__init__.pyc0000644000175000017500000000054112617463171020707 0ustar fedefede00000000000000 e>Vc@s8ddlmZmZmZmZmZmZmZdS(i(tCorkt JsonBackendt AAAExceptiont AuthExceptiontMailert FlaskCorktRedirectN(tcorkRRRRRRR(((s>/home/fede/newhome/projects/bottle-cork/tests/cork/__init__.pytsbottle-cork-0.12.0/examples/cork/.mongodb_backend.py.swp0000644000175000017500000005000012517673724022770 0ustar fedefede00000000000000b0VIM 7.4>>Ur 4[fedeneko~fede/newhome/projects/bottle-cork/cork/mongodb_backend.pyutf-8 3210#"! UtpX4V(~ad@XNMI-T,#"! q O / .  y o n W 5 4  | c )  o n ; /   c < " gNF {Q(@ vG!yxWk+*n`;: spec = {key_name: key_val} data[key_name] = key_val else: assert data[key_name] == key_val if key_name in data: key_name = self._key_name assert isinstance(data, dict) def __setitem__(self, key_val, data): super(MongoMultiValueTable, self).__init__(*args, **kw) def __init__(self, *args, **kw): """ """MongoDB table accessible as a dictionary.class MongoMultiValueTable(MongoTable): self._parent[self._root_key] = self super(MongoMutableDict, self).__setitem__(k, v) def __setitem__(self, k, v): self._root_key = root_key self._parent = parent super(MongoMutableDict, self).__init__(d) """ :type parent: :class:`MongoTable` :param parent: Table instance """Create a MongoMutableDict instance. def __init__(self, parent, root_key, d): """ """Represent an item from a Table. Acts as a dictionary.class MongoMutableDict(dic self._coll.update(spec, {'$set': data}, upsert=Tr self._coll.update_one(spec, {'$set': data}, upsert=True) else: self._coll.update(spec, {'$set': data}, upsert=True, w=1) if is_pymongo_2: data = {self._key_name: key_val, 'val': data} spec = {self._key_name: key_val} assert not isinstance(data, dict) def __setitem__(self, key_val, data): super(MongoSingleValueTable, self).__init__(*args, **kw) def __init__(self, *args, **kw): # Values are stored in a MongoDB "column" named "val" """ Used to store roles. """MongoDB table accessible as a simple key -> value dictionary.class MongoSingleValueTable(MongoTable): return r self._coll.remove({self._key_name: key_val}, w=1) r = self[key_val] """Remove a dictionary item""" def pop(self, key_val): yield (i[self._key_name], d) d.pop('_id') d.pop(self._key_name) d = i.copy() for i in r: r = self._coll.find() """ :returns: generator of (key, value) tuples """Iter on dictionary items. def iteritems(self): return (i[self._key_name] for i in r) r = self._coll.find(projection=[self._key_name,]) else: r = self._coll.find(fields=[self._key_name,]) if is_pymongo_2: """Iter on dictionary keys""" def __iter__(self): return r is not None r = self._coll.find_one({self._key_name: value}) def __contains__(self, value): return self._coll.count() def __len__(self): ) unique=True, drop_dups=True, self._key_name, self._coll.create_index( """Create collection index.""" def create_index(self): self._coll = collection self._key_name = key_name self._name = name def __init__(self, name, key_name, collection): """ Allow dictionary-like access. """Abstract MongoDB Table.class MongoTable(Table): passexcept ImportError: # pragma: no cover is_pymongo_2 = (pymongo.version_tuple[0] == 2) import pymongotry:from .base_backend import Backend, Tablelog = getLogger(__name__)from logging import getLogger""" :synopsis: MongoDB storage backend... module:: mongodb_backend"""# Released under LGPLv3+ license, see LICENSE.txt# Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file.# Cork - Authentication module for the Bottle web frameworkadI  (fe321o,  s 8  z y b ; :  r e d J = <   \ O + pass def save_pen pass def save_pen pass def save_pending_registrations(self): pass def save_rol pass pass pass def save_pending_registrations(self): pass pass pass def save_pending_registrations(self): pass def save pass def save_pending_registrations(self): pass def save_roles(self): pass def save_users(self): c.create_index() for c in (self.users, self.roles, self.pending_registrations): """Create MongoDB indexes.""" def _initialize_storage(self): self._initialize_storage() if initialize: self.roles = MongoSingleValueTable('roles', 'role', db.roles) ) db.pending_registrations 'pending_registration', 'pending_registrations', self.pending_registrations = MongoMultiValueTable( self.users = MongoMultiValueTable('users', 'login', db.users) db.authenticate(username, password) if username and password: db = connection[db_name] connection = pymongo.MongoClient(host=hostname, port=port) """Initialize MongoDB Backend""" def __init__(self, db_name='cork', hostname='localhost', port=27017, initialize=False, username=None, password=None):class MongoDBBackend(Backend): return MongoMutableDict(self, key_val, r) raise KeyError(key_val) if r is None: r = self._coll.find_one({self._key_name: key_val}) def __getitem__(self, key_val):ad 4feML.g=1 e -  d c b a 9  p J ( l N M 4 c  prin #r #r = self._coll self._c self._coll.update_one(spec, {'$set': data}, self._coll.update_one(spec, {'$set': data}, upsert=True) else: self._coll.update(spec, {'$set': data}, upsert=True, w=1) if is_pymongo_2: del(data[u'_id']) if u'_id' in data: spec = {key_name: key_val} data[key_name] = key_val else: assert data[key_name] == key_val if key_name in data: key_name = self._key_name assert isinstance(data, dict) def __setitem__(self, key_val, data): super(MongoMultiValueTable, self).__init__(*args, **kw) def __init__(self, *args, **kw): """ """MongoDB table accessible as a dictionary.class MongoMultiValueTable(MongoTable): r = self._parent._coll.update_one(spec, {'$set': {k: v}}, upsert=True) else: r = self._parent._coll.update(spec, {'$set': {k: v}}, upsert=True) if is_pymongo_2: spec = {self._parent._key_name: self._root_key} super(MongoMutableDict, self).__setitem__(k, v) def __setitem__(self, k, v): self._root_key = root_key self._parent = parent super(MongoMutableDict, self).__init__(d) """ :type parent: :class:`MongoTable` :param parent: Table instance """Create a MongoMutableDict instance. def __init__(self, parent, root_key, d): """ """Represent an item from a Table. Acts as a dictionary.class MongoMutableDict(dict): return r['val'] raise KeyError(key_val) if r is None: r = self._coll.find_one({self._key_name: key_val}) def __getitem__(self, key_val):bottle-cork-0.12.0/examples/simple_webapp.py0000755000175000017500000001201112345430156020667 0ustar fedefede00000000000000#!/usr/bin/env python # # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Cork example web application # # The following users are already available: # admin/admin, demo/demo import bottle from beaker.middleware import SessionMiddleware from cork import Cork import logging logging.basicConfig(format='localhost - - [%(asctime)s] %(message)s', level=logging.DEBUG) log = logging.getLogger(__name__) bottle.debug(True) # Use users.json and roles.json in the local example_conf directory aaa = Cork('example_conf', email_sender='federico.ceratto@gmail.com', smtp_url='smtp://smtp.magnet.ie') app = bottle.app() session_opts = { 'session.cookie_expires': True, 'session.encrypt_key': 'please use a random key and keep it secret!', 'session.httponly': True, 'session.timeout': 3600 * 24, # 1 day 'session.type': 'cookie', 'session.validate_key': True, } app = SessionMiddleware(app, session_opts) # # Bottle methods # # def postd(): return bottle.request.forms def post_get(name, default=''): return bottle.request.POST.get(name, default).strip() @bottle.post('/login') def login(): """Authenticate users""" username = post_get('username') password = post_get('password') aaa.login(username, password, success_redirect='/', fail_redirect='/login') @bottle.route('/user_is_anonymous') def user_is_anonymous(): if aaa.user_is_anonymous: return 'True' return 'False' @bottle.route('/logout') def logout(): aaa.logout(success_redirect='/login') @bottle.post('/register') def register(): """Send out registration email""" aaa.register(post_get('username'), post_get('password'), post_get('email_address')) return 'Please check your mailbox.' @bottle.route('/validate_registration/:registration_code') def validate_registration(registration_code): """Validate registration, create user account""" aaa.validate_registration(registration_code) return 'Thanks. Go to login' @bottle.post('/reset_password') def send_password_reset_email(): """Send out password reset email""" aaa.send_password_reset_email( username=post_get('username'), email_addr=post_get('email_address') ) return 'Please check your mailbox.' @bottle.route('/change_password/:reset_code') @bottle.view('password_change_form') def change_password(reset_code): """Show password change form""" return dict(reset_code=reset_code) @bottle.post('/change_password') def change_password(): """Change password""" aaa.reset_password(post_get('reset_code'), post_get('password')) return 'Thanks. Go to login' @bottle.route('/') def index(): """Only authenticated users can see this""" aaa.require(fail_redirect='/login') return 'Welcome! Admin page Logout' @bottle.route('/restricted_download') def restricted_download(): """Only authenticated users can download this file""" aaa.require(fail_redirect='/login') return bottle.static_file('static_file', root='.') @bottle.route('/my_role') def show_current_user_role(): """Show current user role""" session = bottle.request.environ.get('beaker.session') print "Session from simple_webapp", repr(session) aaa.require(fail_redirect='/login') return aaa.current_user.role # Admin-only pages @bottle.route('/admin') @bottle.view('admin_page') def admin(): """Only admin users can see this""" aaa.require(role='admin', fail_redirect='/sorry_page') return dict( current_user=aaa.current_user, users=aaa.list_users(), roles=aaa.list_roles() ) @bottle.post('/create_user') def create_user(): try: aaa.create_user(postd().username, postd().role, postd().password) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_user') def delete_user(): try: aaa.delete_user(post_get('username')) return dict(ok=True, msg='') except Exception, e: print repr(e) return dict(ok=False, msg=e.message) @bottle.post('/create_role') def create_role(): try: aaa.create_role(post_get('role'), post_get('level')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) @bottle.post('/delete_role') def delete_role(): try: aaa.delete_role(post_get('role')) return dict(ok=True, msg='') except Exception, e: return dict(ok=False, msg=e.message) # Static pages @bottle.route('/login') @bottle.view('login_form') def login_form(): """Serve login form""" return {} @bottle.route('/sorry_page') def sorry_page(): """Serve sorry page""" return '

Sorry, you are not authorized to perform this action

' # # Web application main # # def main(): # Start the Bottle webapp bottle.debug(True) bottle.run(app=app, quiet=False, reloader=True) if __name__ == "__main__": main() bottle-cork-0.12.0/examples/bugtest.py0000644000175000017500000000334212312136477017524 0ustar fedefede00000000000000from bottle import request, route, template, default_app, run from beaker.middleware import SessionMiddleware from cork import Cork app = default_app() aaa = Cork('example_conf') app = SessionMiddleware(app, { 'beaker.session.auto': True, 'beaker.session.type': 'cookie', 'beaker.session.validate_key': True, 'beaker.session.cookie_expires': True, 'beaker.session.timeout': 3600 * 24}) @route('/createuser') def createuser(): tstamp = str(datetime.utcnow()) username = 'root' password = 'pwd' cork._store.users[username] = { 'role': 'admin', 'hash': cork._hash(username, password), 'email_addr': username + '@localhost.local', 'desc': username + ' test user', 'creation_date': tstamp } return "User created" @route('/login', method=['GET', 'POST']) def login(): if request.method == 'GET': return template("""
""") if request.method == 'POST': username = request.POST.get('user','') password = request.POST.get('password','') print username, password aaa.login(username=username, password=password, success_redirect='/', fail_redirect='/login') @route('/') def home(): aaa.require(role='admin', fail_redirect='/login') return 'welcome' run(app=app, host='0.0.0.0', port=8888) bottle-cork-0.12.0/examples/simple_webapp_flask.py0000755000175000017500000001267012314377543022070 0ustar fedefede00000000000000#!/usr/bin/env python # # Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file. # Released under LGPLv3+ license, see LICENSE.txt # # Cork example web application # # The following users are already available: # admin/admin, demo/demo import flask from beaker.middleware import SessionMiddleware from cork import FlaskCork import logging import os logging.basicConfig(format='localhost - - [%(asctime)s] %(message)s', level=logging.DEBUG) log = logging.getLogger(__name__) # Use users.json and roles.json in the local example_conf directory aaa = FlaskCork('example_conf', email_sender='federico.ceratto@gmail.com', smtp_url='smtp://smtp.magnet.ie') app = flask.Flask(__name__) app.debug = True app.options = {} #FIXME from flask import jsonify #session_opts = { # 'session.cookie_expires': True, # 'session.encrypt_key': 'please use a random key and keep it secret!', # 'session.httponly': True, # 'session.timeout': 3600 * 24, # 1 day # 'session.type': 'cookie', # 'session.validate_key': True, #} #app = SessionMiddleware(app, session_opts) # # Bottle methods # # def post_get(name, default=''): v = flask.request.form.get(name, default).strip() return str(v) from cork import Redirect @app.errorhandler(Redirect) def redirect_exception_handler(e): return flask.redirect(e.message) @app.route('/login', methods=['POST']) def login(): """Authenticate users""" username = post_get('username') password = post_get('password') aaa.login(username, password, success_redirect='/', fail_redirect='/login') @app.route('/user_is_anonymous') def user_is_anonymous(): if aaa.user_is_anonymous: return 'True' return 'False' @app.route('/logout') def logout(): aaa.logout(success_redirect='/login') @app.route('/register', methods=['POST']) def register(): """Send out registration email""" aaa.register(post_get('username'), post_get('password'), post_get('email_address')) return 'Please check your mailbox.' @app.route('/validate_registration/:registration_code') def validate_registration(registration_code): """Validate registration, create user account""" aaa.validate_registration(registration_code) return 'Thanks. Go to login' @app.route('/reset_password', methods=['POST']) def send_password_reset_email(): """Send out password reset email""" aaa.send_password_reset_email( username=post_get('username'), email_addr=post_get('email_address') ) return 'Please check your mailbox.' @app.route('/change_password/:reset_code') def change_password(reset_code): """Show password change form""" return flask.render_template('password_change_form', reset_code=reset_code) @app.route('/change_password', methods=['POST']) def do_change_password(): """Change password""" aaa.reset_password(post_get('reset_code'), post_get('password')) return 'Thanks. Go to login' @app.route('/') def index(): """Only authenticated users can see this""" aaa.require(fail_redirect='/login') return 'Welcome! Admin page Logout' @app.route('/restricted_download') def restricted_download(): """Only authenticated users can download this file""" aaa.require(fail_redirect='/login') return flask.static_file('static_file', root='.') @app.route('/my_role') def show_current_user_role(): """Show current user role""" session = flask.request.environ.get('beaker.session') print "Session from simple_webapp", repr(session) aaa.require(fail_redirect='/login') return aaa.current_user.role # Admin-only pages @app.route('/admin') def admin(): """Only admin users can see this""" aaa.require(role='admin', fail_redirect='/sorry_page') return flask.render_template('admin_page.html', current_user=aaa.current_user, users=aaa.list_users(), roles=aaa.list_roles() ) @app.route('/create_user', methods=['POST']) def create_user(): try: aaa.create_user(post_get('username'), post_get('role'), post_get('password')) return jsonify(ok=True, msg='') except Exception, e: return jsonify(ok=False, msg=e.message) @app.route('/delete_user', methods=['POST']) def delete_user(): try: aaa.delete_user(post_get('username')) return jsonify(ok=True, msg='') except Exception, e: print repr(e) return jsonify(ok=False, msg=e.message) @app.route('/create_role', methods=['POST']) def create_role(): try: aaa.create_role(post_get('role'), post_get('level')) return jsonify(ok=True, msg='') except Exception, e: return jsonify(ok=False, msg=e.message) @app.route('/delete_role', methods=['POST']) def delete_role(): try: aaa.delete_role(post_get('role')) return jsonify(ok=True, msg='') except Exception, e: return jsonify(ok=False, msg=e.message) # Static pages @app.route('/login') def login_form(): """Serve login form""" return flask.render_template('login_form.html') @app.route('/sorry_page') def sorry_page(): """Serve sorry page""" return '

Sorry, you are not authorized to perform this action

' # # Web application main # # app.secret_key = os.urandom(24) #FIXME: why def main(): # Start the Bottle webapp #bottle.debug(True) app.secret_key = os.urandom(24) app.run(debug=True, use_reloader=True) if __name__ == "__main__": main() bottle-cork-0.12.0/examples/views/0000755000175000017500000000000012617467123016633 5ustar fedefede00000000000000bottle-cork-0.12.0/examples/views/login_form.tpl0000644000175000017500000000357112166632355021515 0ustar fedefede00000000000000

Login

Please insert your credentials:




Signup

Please insert your credentials:




Password reset

Please insert your credentials:





bottle-cork-0.12.0/examples/views/registration_email.tpl0000644000175000017500000000056112161350444023226 0ustar fedefede00000000000000Hello {{username}},
You are receiving this email because you registered on Cork Demo Webapp on {{creation_date}}.

If you wish to complete the registration please click on:
Confirm

Your email address is: {{email_addr}}
Your role will be: {{role}}.
bottle-cork-0.12.0/examples/views/password_change_form.tpl0000644000175000017500000000147012161350444023537 0ustar fedefede00000000000000

Password change

Please insert your new password:




bottle-cork-0.12.0/examples/views/password_reset_email.tpl0000644000175000017500000000042412161350444023556 0ustar fedefede00000000000000Hello {{username}},
You are receiving this email because you requested a password reset on Cork Demo Webapp.

If you wish to complete the password reset please click on:
Confirm

bottle-cork-0.12.0/examples/views/admin_page.tpl0000644000175000017500000000761611742570235021447 0ustar fedefede00000000000000

Cork - Administration page

Welcome {{current_user.username}}, your role is: {{current_user.role}}, access time: {{current_user.session_accessed_time}}

Create new user:


Delete user:


Create new role:


Delete role:

%for u in users: %end
UsernameRoleEmailDescription
{{u[0]}}{{u[1]}}{{u[2]}}{{u[2]}}

%for r in roles: %end
RoleLevel
{{r[0]}}{{r[1]}}

(Reload page to refresh)

Ready.

bottle-cork-0.12.0/bottle_cork.egg-info/0000755000175000017500000000000012617467123017661 5ustar fedefede00000000000000bottle-cork-0.12.0/bottle_cork.egg-info/PKG-INFO0000644000175000017500000000224212617467117020761 0ustar fedefede00000000000000Metadata-Version: 1.1 Name: bottle-cork Version: 0.12.0 Summary: Authentication/Authorization library for Bottle Home-page: http://cork.firelet.net/ Author: Federico Ceratto Author-email: federico.ceratto@gmail.com License: LGPLv3+ Description: Cork is a simple Authentication/Authorization libraryfor the Bottle and Flask web frameworks. Platform: Linux Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Framework :: Bottle Classifier: Framework :: Flask Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Natural Language :: English Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Security Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Python Modules bottle-cork-0.12.0/bottle_cork.egg-info/SOURCES.txt0000644000175000017500000000532012617467123021545 0ustar fedefede00000000000000MANIFEST.in README.rst setup.py bottle_cork.egg-info/PKG-INFO bottle_cork.egg-info/SOURCES.txt bottle_cork.egg-info/dependency_links.txt bottle_cork.egg-info/requires.txt bottle_cork.egg-info/top_level.txt cork/__init__.py cork/backends.py cork/base_backend.py cork/cork.py cork/json_backend.py cork/mongodb_backend.py cork/sqlalchemy_backend.py cork/sqlite_backend.py examples/bugtest.py examples/cork.py examples/example.db examples/recreate_example_conf.py examples/simple_webapp.py examples/simple_webapp_decorated.py examples/simple_webapp_flask.py examples/simple_webapp_using_mongodb.py examples/simple_webapp_using_sqlite.py examples/simple_webapp_using_sqlite_mytest.py examples/static_file examples/test_real_ssl_smtp.py examples/cork/.mongodb_backend.py.swp examples/cork/SUMMARY examples/cork/__init__.py examples/cork/__init__.pyc examples/cork/backends.py examples/cork/backends.pyc examples/cork/base_backend.py examples/cork/base_backend.pyc examples/cork/cork.py examples/cork/cork.pyc examples/cork/json_backend.py examples/cork/json_backend.pyc examples/cork/mongodb_backend.py examples/cork/mongodb_backend.py.broken examples/cork/mongodb_backend.py.works examples/cork/mongodb_backend.pyc examples/cork/sqlalchemy_backend.py examples/cork/sqlalchemy_backend.pyc examples/cork/sqlite_backend.py examples/cork/sqlite_backend.pyc examples/cork/__pycache__/__init__.cpython-34.pyc examples/cork/__pycache__/backends.cpython-34.pyc examples/cork/__pycache__/base_backend.cpython-34.pyc examples/cork/__pycache__/cork.cpython-34.pyc examples/cork/__pycache__/json_backend.cpython-34.pyc examples/cork/__pycache__/mongodb_backend.cpython-34.pyc examples/cork/__pycache__/sqlalchemy_backend.cpython-34.pyc examples/cork/__pycache__/sqlite_backend.cpython-34.pyc examples/example_conf/register.json examples/example_conf/roles.json examples/example_conf/users.json examples/templates/admin_page.html examples/templates/login_form.html examples/templates/password_change_form.html examples/templates/password_reset_email.html examples/templates/registration_email.html examples/views/admin_page.tpl examples/views/login_form.tpl examples/views/password_change_form.tpl examples/views/password_reset_email.tpl examples/views/registration_email.tpl tests/conftest.py tests/multiparam.py tests/simple_webapp.py tests/simple_webapp_decorated.py tests/simple_webapp_flask.py tests/test.py tests/test_crypto.py tests/test_flask.py tests/test_foo.py tests/test_functional.py tests/test_mysql.py tests/test_pbkdf2.py tests/test_pymongo.py tests/test_scrypt.py tests/test_set_pass.py tests/test_sqla.py tests/test_sqlalchemy.py tests/test_webtest_bottle.py tests/test_webtest_decorated.py tests/test_webtest_flask.py tests/test_webtest_parallel.py tests/testutils.pybottle-cork-0.12.0/bottle_cork.egg-info/dependency_links.txt0000644000175000017500000000000112617467117023732 0ustar fedefede00000000000000 bottle-cork-0.12.0/bottle_cork.egg-info/requires.txt0000644000175000017500000000005012617467117022257 0ustar fedefede00000000000000Bottle pycrypto [scrypt] scrypt>=0.6.1 bottle-cork-0.12.0/bottle_cork.egg-info/top_level.txt0000644000175000017500000000000512617467117022411 0ustar fedefede00000000000000cork