Skip to content

Commit

Permalink
Merge 4eaf62a into 8ffb5e4
Browse files Browse the repository at this point in the history
  • Loading branch information
MarekSuchanek committed Jul 23, 2018
2 parents 8ffb5e4 + 4eaf62a commit 577278c
Show file tree
Hide file tree
Showing 19 changed files with 308 additions and 79 deletions.
1 change: 1 addition & 0 deletions docs/usage/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Usage
start
commands
web
privileges
3 changes: 3 additions & 0 deletions docs/usage/privileges.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Privileges and Roles
====================

28 changes: 28 additions & 0 deletions migrations/versions/13a18cc60ee5_privileges_for_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Privileges for role
Revision ID: 13a18cc60ee5
Revises: 891cf9712a20
Create Date: 2018-07-21 09:54:25.502179
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '13a18cc60ee5'
down_revision = '891cf9712a20'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('Role', sa.Column('privileges', sa.Text(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('Role', 'privileges')
# ### end Alembic commands ###
3 changes: 2 additions & 1 deletion repocribro/commands/assign_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ def _assign_role(login, role_name):
role = db.session.query(Role).filter_by(name=role_name).first()
if role is None:
print('Role {} not in DB... adding'.format(role_name))
role = Role(role_name, '')
print('WARNING - created role has all privileges by default!')
role = Role(role_name, '*', '')
db.session.add(role)
user.user_account.roles.append(role)
db.session.commit()
Expand Down
57 changes: 38 additions & 19 deletions repocribro/controllers/admin.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import flask
import sqlalchemy

from ..models import User, Role, Repository
from ..security import permissions
from ..models import User, Role, Repository, Anonymous
from ..security import permissions, reload_anonymous_role

#: Admin controller blueprint
admin = flask.Blueprint('admin', __name__, url_prefix='/admin')


@admin.route('')
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def index():
"""Administration zone dashboard (GET handler)"""
ext_master = flask.current_app.container.get('ext_master')
Expand All @@ -26,7 +26,7 @@ def index():


@admin.route('/account/<login>')
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def account_detail(login):
"""Account administration (GET handler)"""
db = flask.current_app.container.get('db')
Expand All @@ -38,7 +38,7 @@ def account_detail(login):


@admin.route('/account/<login>/ban', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def account_ban(login):
"""Ban (make inactive) account (POST handler)"""
db = flask.current_app.container.get('db')
Expand Down Expand Up @@ -66,7 +66,7 @@ def account_ban(login):


@admin.route('/account/<login>/delete', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def account_delete(login):
"""Delete account (POST handler)"""
db = flask.current_app.container.get('db')
Expand All @@ -84,7 +84,7 @@ def account_delete(login):


@admin.route('/repository/<login>/<reponame>')
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def repo_detail(login, reponame):
"""Repository administration (GET handler)"""
db = flask.current_app.container.get('db')
Expand All @@ -101,7 +101,7 @@ def repo_detail(login, reponame):


@admin.route('/repository/<login>/<reponame>/visibility', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def repo_visibility(login, reponame):
"""Change repository visibility (POST handler)"""
db = flask.current_app.container.get('db')
Expand Down Expand Up @@ -134,7 +134,7 @@ def repo_visibility(login, reponame):


@admin.route('/repository/<login>/<reponame>/delete', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def repo_delete(login, reponame):
"""Delete repository (POST handler)"""
db = flask.current_app.container.get('db')
Expand All @@ -152,7 +152,7 @@ def repo_delete(login, reponame):


@admin.route('/role/<name>')
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def role_detail(name):
"""Role administration (GET handler)"""
db = flask.current_app.container.get('db')
Expand All @@ -164,7 +164,7 @@ def role_detail(name):


@admin.route('/role/<name>/edit', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def role_edit(name):
"""Edit role (POST handler)"""
db = flask.current_app.container.get('db')
Expand All @@ -173,14 +173,24 @@ def role_edit(name):
if role is None:
flask.abort(404)
name = flask.request.form.get('name', '')
priv = flask.request.form.get('privileges', '')
desc = flask.request.form.get('description', None)
if name == '':
flask.flash('Couldn\'t make that role...', 'warning')
return flask.redirect(flask.url_for('admin.index', tab='roles'))

role.name = name
role.privileges = priv.lower()
role.description = desc
if not role.valid_privileges():
flask.flash('Unsaved - incorrect characters in privileges '
'for role {}'.format(name), 'warning')
return flask.redirect(flask.url_for('admin.role_detail',
name=role.name))
try:
role.name = name
role.description = desc
db.session.commit()
if name == Anonymous.rolename:
reload_anonymous_role(flask.current_app, db)
except sqlalchemy.exc.IntegrityError as e:
flask.flash('Couldn\'t make that role... {}'.format(str(e)),
'warning')
Expand All @@ -191,7 +201,7 @@ def role_edit(name):


@admin.route('/role/<name>/delete', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def role_delete(name):
"""Delete role (POST handler)"""
db = flask.current_app.container.get('db')
Expand All @@ -207,30 +217,39 @@ def role_delete(name):


@admin.route('/roles/create', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def role_create():
"""Create new role (POST handler)"""
db = flask.current_app.container.get('db')

name = flask.request.form.get('name', '')
priv = flask.request.form.get('privileges', '')
desc = flask.request.form.get('description', None)
if name == '':
flask.flash('Couldn\'t make that role...', 'warning')
return flask.redirect(flask.url_for('admin.index', tab='roles'))
role = Role(name, priv, desc)
if not role.valid_privileges():
flask.flash('Unsaved - incorrect characters in privileges '
'for role {}'.format(name), 'warning')
return flask.redirect(flask.url_for('admin.role_detail',
name=role.name))
try:
role = Role(name, desc)
db.session.add(role)
db.session.commit()
if name == Anonymous.rolename:
reload_anonymous_role(flask.current_app, db)
except sqlalchemy.exc.IntegrityError as e:
flask.flash('Couldn\'t make that role... {}'.format(str(e)),
'warning')
db.session.rollback()
return flask.redirect(flask.url_for('admin.index', tab='roles'))
return flask.redirect(flask.url_for('admin.role_detail', name=role.name))
return flask.redirect(flask.url_for('admin.role_detail',
name=role.name))


@admin.route('/role/<name>/add', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def role_assignment_add(name):
"""Assign role to user (POST handler)"""
db = flask.current_app.container.get('db')
Expand All @@ -253,7 +272,7 @@ def role_assignment_add(name):


@admin.route('/role/<name>/remove', methods=['POST'])
@permissions.admin_role.require(404)
@permissions.roles.admin.require(404)
def role_assignment_remove(name):
"""Remove assignment of role to user (POST handler)"""
db = flask.current_app.container.get('db')
Expand Down
7 changes: 6 additions & 1 deletion repocribro/controllers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import flask_sqlalchemy

from ..models import User, UserAccount
from ..security import login as security_login, logout as security_logout
from ..security import login as security_login, \
logout as security_logout, \
get_default_user_role

#: Auth controller blueprint
auth = flask.Blueprint('auth', __name__, url_prefix='/auth')
Expand Down Expand Up @@ -33,6 +35,9 @@ def github_callback_get_account(db, gh_api):
is_new = False
if gh_user is None:
user_account = UserAccount()
default_role = get_default_user_role(flask.current_app, db)
if default_role is not None:
user_account.roles.append(default_role)
db.session.add(user_account)
gh_user = User.create_from_dict(user_data, user_account)
db.session.add(gh_user)
Expand Down
1 change: 1 addition & 0 deletions repocribro/controllers/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import flask_login

from ..models import User, Organization, Repository
from ..security import permissions

#: Core controller blueprint
core = flask.Blueprint('core', __name__, url_prefix='')
Expand Down
20 changes: 18 additions & 2 deletions repocribro/ext_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from .extending import Extension
from .extending.helpers import ViewTab, Badge
from .models import Push, Release, Repository
from .models import Push, Release, Repository, Role, Anonymous, UserAccount
from .github import GitHubAPI


Expand Down Expand Up @@ -164,6 +164,20 @@ def provide_filters():
from .filters import all_filters
return all_filters

@staticmethod
def provide_roles():
return {
'admin': Role('admin', '*', 'Service administrators'),
'user': Role(UserAccount.default_rolename, 'search*',
'Regular users'),
'anonymous': Role(Anonymous.rolename, 'search*:login',
'Not-logged users')
}

@staticmethod
def provide_actions():
return ['login', 'search']

@staticmethod
def get_gh_webhook_processors():
"""Get all GitHub webhooks processory"""
Expand Down Expand Up @@ -191,7 +205,9 @@ def setup_config(self):

def init_business(self):
"""Init business layer (other extensions, what is needed)"""
from .security import init_login_manager
from .security import init_login_manager, reload_anonymous_role

reload_anonymous_role(self.app, self.db)
login_manager, principals = init_login_manager(self.db)
login_manager.init_app(self.app)
principals.init_app(self.app)
Expand Down
28 changes: 28 additions & 0 deletions repocribro/extending/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,24 @@ def provide_filters():
"""
return {}

@staticmethod
def provide_roles():
"""Extension can define roles for user accounts
:return: Dictionary with name + Role entity
:rtype: dict of str: ``repocribro.models.Role``
"""
return {}

@staticmethod
def provide_actions():
"""Extension can define actions for privileges
:return: List of action names
:rtype: list of str
"""
return []

def init_models(self):
"""Hook operation for initiating the models and registering them
within db
Expand All @@ -126,6 +144,16 @@ def init_filters(self):
"""
self.register_filters_from_dict(self.provide_filters())

def init_security(self):
"""Hook operation to setup privileges (roles and actions)"""
from ..security import create_default_role
permissions = self.app.container.get('permissions')
for role_name, role in self.provide_roles().items():
permissions.register_role(role_name)
create_default_role(self.app, self.db, role)
for action_name in self.provide_actions():
permissions.register_action(action_name)

def introduce(self):
"""Hook operation for getting short introduction of extension (mostly
for debug/log purpose)
Expand Down

0 comments on commit 577278c

Please sign in to comment.