Source code for users

'''
The `users` package manages the models and the API about users, groups and
capabilities. Note that this package does **not** specify permissions for
objects. Actual permissions are handled at the UI level.

The main concepts are:

- A :py:class:`~users.models.User` is what you think it is; something that you can login as.
- A :py:class:`~users.models.Group` is a collection of users. Note that a user can belong to multiple
  groups. A group has capabilities.
- A :py:class:`~users.models.Capability` is a "granted permission". You can think of it like a piece of
  paper saying, ie. "you can create new attachments".

  - Its :py:attr:`~users.models.Capability.action` is a composition of Create, Read, Update, Delete
    (it follows the CRUD model).
  - A :py:attr:`~users.models.Capability.domain` is a regular expression that
    must "match" to the description of an object. ie. ``/cars/*`` means "every
    car", while ``/cars/*/tires/`` means "the tires of every car"

This also means that a user has no capability (directly). It just belongs to
groups, which, in turn, have capabilities.

The rationale behind what a Capability is may seem baroque, but there are
several advantages to it:

- it is decoupled from  the actual domains used by the UI
- the regular expression make it possible to create groups that can operate on
  everything (``*``).
'''
from peewee import SqliteDatabase
from playhouse import db_url
from passlib.context import CryptContext
from models import db_proxy,\
    User, Group, UserToGroup, GroupToCapability, Capability, Action
import logging


[docs]class SqliteFKDatabase(SqliteDatabase): '''SqliteDatabase with foreignkey support enabled'''
[docs] def initialize_connection(self, conn): self.execute_sql('PRAGMA foreign_keys=ON;')
# replace default sqlite scheme # in order to support foreign key db_url.schemes['sqlite'] = SqliteFKDatabase # context to be use for password hashing # it's initialized and configured by pwdCryptCtx=None db = db_proxy
[docs]def init_proxy(dbURL): '''Instantiate proxy to the database :param dbURL: the url describing connection parameters to the choosen database. The url must have format explained in the `Peewee url documentation <http://peewee.readthedocs.org/en/latest/peewee/playhouse.html#db-url>`_. examples: * sqlite: ``sqlite:///my_database.db`` * postgres: ``postgresql://postgres:my_password@localhost:5432/my_database`` * mysql: ``mysql://user:passwd@ip:port/my_db`` ''' db_proxy.initialize(db_url.connect(dbURL)) return db_proxy
[docs]def create_tables(database): '''Create all tables in the given database''' logging.getLogger(__name__).debug("Creating missing database tables") database.connect() database.create_tables([User, Group, UserToGroup, GroupToCapability, Capability], safe=True)
[docs]def populate_with_defaults(): '''Create user admin and grant him all permission If the admin user already exists the function will simply return ''' logging.getLogger(__name__).debug("Populating with default users") if not User.select().where(User.name == 'admin').exists(): admin = User.create(name='admin', password='admin') admins = Group.create(name='admins') starCap = Capability.create(domain='.+', action=(Action.CREATE | Action.READ | Action.UPDATE | Action.DELETE)) admins.capabilities.add(starCap) admin.groups.add(admins) admin.save() if not User.select().where(User.name == 'anonymous').exists(): anon = User.create(name='anonymous', password='') anons = Group.create(name='anonymous') readCap = Capability.create(domain=Capability.simToReg('/volumes/*'), action=Action.READ) anons.capabilities.add(readCap) anon.groups.add(anons) anon.save()
[docs]def init_db(dbURL, pwd_salt_size=None, pwd_rounds=None): '''Initialize users database initialize database and create necessary tables to handle users oprations. :param dbURL: database url, as described in :func:`init_proxy` ''' if not dbURL: dbURL = 'sqlite:///:memory:' logging.getLogger(__name__).debug("Initializing database: {}".format(dict(url=dbURL, pwd_salt_size=pwd_salt_size, pwd_rounds=pwd_rounds))) try: db = init_proxy(dbURL) global pwdCryptCtx pwdCryptCtx = gen_crypt_context(salt_size=pwd_salt_size, rounds=pwd_rounds) create_tables(db) return db except Exception as e: e.args = (e.args[0] + ' [users database]',) raise
[docs]def gen_crypt_context(salt_size=None, rounds=None): return CryptContext(schemes=['pbkdf2_sha256', 'pbkdf2_sha512'], default='pbkdf2_sha256', all__salt_size=salt_size, all__default_rounds=rounds)