mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-15 01:05:42 +00:00
Add Roles and Permission models so that only admins can access /admin
This commit is contained in:
parent
66d9ab8a93
commit
79cccd36ee
19
app/decorators.py
Normal file
19
app/decorators.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from functools import wraps
|
||||||
|
from flask import abort
|
||||||
|
from flask_login import current_user
|
||||||
|
from .models import Permission
|
||||||
|
|
||||||
|
|
||||||
|
def permission_required(permission):
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if not current_user.can(permission):
|
||||||
|
abort(403)
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def admin_required(f):
|
||||||
|
return permission_required(Permission.ADMIN)(f)
|
@ -2,4 +2,11 @@ from flask import Blueprint
|
|||||||
|
|
||||||
main = Blueprint('main', __name__)
|
main = Blueprint('main', __name__)
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
from . import views, errors
|
||||||
|
from ..models import Permission
|
||||||
|
|
||||||
|
|
||||||
|
@main.app_context_processor
|
||||||
|
def inject_permissions():
|
||||||
|
return dict(Permission=Permission)
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
from flask import render_template
|
from flask import render_template
|
||||||
from . import main
|
from . import main
|
||||||
|
from ..decorators import admin_required
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
|
||||||
@main.route('/')
|
@main.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('main/index.html.j2')
|
return render_template('main/index.html.j2')
|
||||||
|
|
||||||
|
|
||||||
|
@main.route('/admin')
|
||||||
|
@login_required
|
||||||
|
@admin_required
|
||||||
|
def for_admins_only():
|
||||||
|
return "For administrators!"
|
||||||
|
@ -1,19 +1,69 @@
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import UserMixin
|
from flask_login import UserMixin, AnonymousUserMixin
|
||||||
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
|
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from . import db
|
from . import db
|
||||||
from . import login_manager
|
from . import login_manager
|
||||||
|
|
||||||
|
|
||||||
|
class Permission:
|
||||||
|
CREATE_JOB = 1
|
||||||
|
DELETE_JOB = 2
|
||||||
|
# WRITE = 4
|
||||||
|
# MODERATE = 8
|
||||||
|
ADMIN = 16
|
||||||
|
|
||||||
|
|
||||||
class Role(db.Model):
|
class Role(db.Model):
|
||||||
__tablename__ = 'roles'
|
__tablename__ = 'roles'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(64), unique=True)
|
name = db.Column(db.String(64), unique=True)
|
||||||
|
default = db.Column(db.Boolean, default=False, index=True)
|
||||||
|
permissions = db.Column(db.Integer)
|
||||||
|
users = db.relationship('User', backref='role', lazy='dynamic')
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(Role, self).__init__(**kwargs)
|
||||||
|
if self.permissions is None:
|
||||||
|
self.permissions = 0
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Role %r>' % self.name
|
return '<Role %r>' % self.name
|
||||||
|
|
||||||
|
def add_permission(self, perm):
|
||||||
|
if not self.has_permission(perm):
|
||||||
|
self.permissions += perm
|
||||||
|
|
||||||
|
def remove_permission(self, perm):
|
||||||
|
if self.has_permission(perm):
|
||||||
|
self.permissions -= perm
|
||||||
|
|
||||||
|
def reset_permissions(self):
|
||||||
|
self.permissions = 0
|
||||||
|
|
||||||
|
def has_permission(self, perm):
|
||||||
|
return self.permissions & perm == perm
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def insert_roles():
|
||||||
|
roles = {
|
||||||
|
'User': [Permission.CREATE_JOB],
|
||||||
|
'Administrator': [Permission.ADMIN,
|
||||||
|
Permission.CREATE_JOB,
|
||||||
|
Permission.DELETE_JOB]
|
||||||
|
}
|
||||||
|
default_role = 'User'
|
||||||
|
for r in roles:
|
||||||
|
role = Role.query.filter_by(name=r).first()
|
||||||
|
if role is None:
|
||||||
|
role = Role(name=r)
|
||||||
|
role.reset_permissions()
|
||||||
|
for perm in roles[r]:
|
||||||
|
role.add_permission(perm)
|
||||||
|
role.default = (role.name == default_role)
|
||||||
|
db.session.add(role)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
class User(UserMixin, db.Model):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
@ -27,6 +77,14 @@ class User(UserMixin, db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<User %r>' % self.username
|
return '<User %r>' % self.username
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(User, self).__init__(**kwargs)
|
||||||
|
if self.role is None:
|
||||||
|
if self.email == current_app.config['OPAQUE_ADMIN']:
|
||||||
|
self.role = Role.query.filter_by(name='Administrator').first()
|
||||||
|
if self.role is None:
|
||||||
|
self.role = Role.query.filter_by(default=True).first()
|
||||||
|
|
||||||
def generate_confirmation_token(self, expiration=3600):
|
def generate_confirmation_token(self, expiration=3600):
|
||||||
s = Serializer(current_app.config['SECRET_KEY'], expiration)
|
s = Serializer(current_app.config['SECRET_KEY'], expiration)
|
||||||
return s.dumps({'confirm': self.id}).decode('utf-8')
|
return s.dumps({'confirm': self.id}).decode('utf-8')
|
||||||
@ -72,6 +130,23 @@ class User(UserMixin, db.Model):
|
|||||||
def verify_password(self, password):
|
def verify_password(self, password):
|
||||||
return check_password_hash(self.password_hash, password)
|
return check_password_hash(self.password_hash, password)
|
||||||
|
|
||||||
|
def can(self, perm):
|
||||||
|
return self.role is not None and self.role.has_permission(perm)
|
||||||
|
|
||||||
|
def is_administrator(self):
|
||||||
|
return self.can(Permission.ADMIN)
|
||||||
|
|
||||||
|
|
||||||
|
class AnonymousUser(AnonymousUserMixin):
|
||||||
|
def can(self, permissions):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_administrator(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
login_manager.anonymous_user = AnonymousUser # Flask-Login is told to use the application’s custom anonymous user by setting its class in the login_manager.anonymous_user attribute.
|
||||||
|
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
|
12
app/templates/main/admin.html.j2
Normal file
12
app/templates/main/admin.html.j2
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% extends "base.html.j2" %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<h1>Administration tools</h1>
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card large">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">User list</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -11,6 +11,7 @@ class Config:
|
|||||||
['true', 'on', '1']
|
['true', 'on', '1']
|
||||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||||
|
OPAQUE_ADMIN = os.environ.get('OPAQUE_ADMIN')
|
||||||
OPAQUE_MAIL_SUBJECT_PREFIX = '[Opaque]'
|
OPAQUE_MAIL_SUBJECT_PREFIX = '[Opaque]'
|
||||||
OPAQUE_MAIL_SENDER = 'Opaque Development <dev.opaque@gmail.com>'
|
OPAQUE_MAIL_SENDER = 'Opaque Development <dev.opaque@gmail.com>'
|
||||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
|
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
|
||||||
|
32
migrations/versions/01a7d98d9647_.py
Normal file
32
migrations/versions/01a7d98d9647_.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 01a7d98d9647
|
||||||
|
Revises: 69f5d9c59c34
|
||||||
|
Create Date: 2019-07-09 10:59:08.639902
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '01a7d98d9647'
|
||||||
|
down_revision = '69f5d9c59c34'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('roles', sa.Column('default', sa.Boolean(), nullable=True))
|
||||||
|
op.add_column('roles', sa.Column('permissions', sa.Integer(), nullable=True))
|
||||||
|
op.create_index(op.f('ix_roles_default'), 'roles', ['default'], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_roles_default'), table_name='roles')
|
||||||
|
op.drop_column('roles', 'permissions')
|
||||||
|
op.drop_column('roles', 'default')
|
||||||
|
# ### end Alembic commands ###
|
@ -1,5 +1,5 @@
|
|||||||
from app import create_app, db
|
from app import create_app, db
|
||||||
from app.models import User, Role
|
from app.models import User, Role, Permission
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ migrate = Migrate(app, db)
|
|||||||
|
|
||||||
@app.shell_context_processor
|
@app.shell_context_processor
|
||||||
def make_shell_context():
|
def make_shell_context():
|
||||||
return dict(db=db, User=User, Role=Role)
|
return dict(db=db, User=User, Role=Role, Permission=Permission)
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
|
Loading…
Reference in New Issue
Block a user