From d61c395d2c64352e1fbad9b2e65c7c2de077bbb6 Mon Sep 17 00:00:00 2001 From: tobias Date: Wed, 23 Oct 2019 16:33:24 +0200 Subject: [PATCH] added access control model and more stuff around access mgmt --- backend/__main__.py | 1 - backend/api/user_api.py | 6 ++- backend/config.py | 5 +- backend/models/__init__.py | 1 + backend/models/access_control_model.py | 73 ++++++++++++++++++++++++++ backend/models/user_model.py | 59 ++++++++++++--------- 6 files changed, 117 insertions(+), 28 deletions(-) create mode 100644 backend/models/access_control_model.py diff --git a/backend/__main__.py b/backend/__main__.py index 24c6ba3..75a0fbc 100644 --- a/backend/__main__.py +++ b/backend/__main__.py @@ -33,7 +33,6 @@ def main(): pre_fill_table() update_recorder_models_database() - app.run(debug=True, host="0.0.0.0") diff --git a/backend/api/user_api.py b/backend/api/user_api.py index 2d0c2ee..4b933d0 100644 --- a/backend/api/user_api.py +++ b/backend/api/user_api.py @@ -22,13 +22,17 @@ user_model = api_user.model('User', { 'last_name': fields.String(required=True, description='The user\'s last name'), 'email': fields.String(required=True, description='The user\'s email address'), 'nickname': fields.String(required=False, description='The user\'s nick name'), + 'role': fields.String(required=False, description='Role a user might have (in addition to group memberships)'), + 'effective_permissions': fields.List( + fields.String(required=True), required=False, description="List of permissions (groups + (optional) role)." + ), 'groups': fields.List( fields.Nested(api_user.model('user_group', {'id': fields.Integer(), 'name': fields.String()})), required=False, description='Group memberships.'), }) user_update_parser = api_user.parser() -user_update_parser.add_argument('email', type=inputs.email, required=False, nullable=False, store_missing=False) +user_update_parser.add_argument('email', type=inputs.email(), required=False, nullable=False, store_missing=False) user_update_parser.add_argument('nickname', type=str, required=False, store_missing=False) user_update_parser.add_argument('first_name', type=str, required=False, store_missing=False) user_update_parser.add_argument('last_name', type=str, required=False, store_missing=False) diff --git a/backend/config.py b/backend/config.py index cdc10fc..c1baec3 100644 --- a/backend/config.py +++ b/backend/config.py @@ -6,7 +6,7 @@ import os basedir = os.path.abspath(os.path.dirname(__file__)) -class Config(): +class Config: # SERVER_NAME = "ubkaps154.ubka.uni-karlsruhe.de:5443" # SERVER_NAME = "localhost.dev" SERVER_NAME = "localhost:5443" @@ -97,6 +97,9 @@ class Config(): "password": "admin"} ] + ROLE_PERMISSION_MAPPINGS = { + "admin": ["ADMIN"] + } class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo' diff --git a/backend/models/__init__.py b/backend/models/__init__.py index 93e79ca..6ee72fe 100644 --- a/backend/models/__init__.py +++ b/backend/models/__init__.py @@ -5,5 +5,6 @@ from backend.models.example_model import * from backend.models.user_model import * from backend.models.post_model import * from backend.models.recorder_model import * +from backend.models.access_control_model import * from backend.models.room_model import * from backend.models.virtual_command_model import * diff --git a/backend/models/access_control_model.py b/backend/models/access_control_model.py new file mode 100644 index 0000000..2a8812c --- /dev/null +++ b/backend/models/access_control_model.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +""" +Models for lecture recorder +""" +import json + +from sqlalchemy import MetaData, CheckConstraint +from sqlalchemy.exc import IntegrityError + +from datetime import datetime, timedelta + +from backend import db, app, login_manager +from backend.tools.scrape_rooms import scrape_rooms + +metadata = MetaData() + + +class AccessControlEntry(db.Model): + id = db.Column(db.Integer, autoincrement=True, primary_key=True) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow()) + name = db.Column(db.Unicode(127), unique=False, nullable=False) + url = db.Column(db.Unicode(2047), unique=False, nullable=True, default="") + + required_permission_id = db.Column(db.Integer, db.ForeignKey('permission.id')) + required_permission = db.relationship('Permission', back_populates='access_control_entry') + + __table_args__ = ( + CheckConstraint('length(name) > 2', + name='name_min_length'), + ) + + def __init__(self, **kwargs): + super(AccessControlEntry, self).__init__(**kwargs) + + @staticmethod + def get_by_name(name): + """ + Find ace by name + :param name: + :return: + """ + return AccessControlEntry.query.filter(AccessControlEntry.name == name).first() + + @staticmethod + def get_all(): + """ + Return all ace + :return: + """ + return AccessControlEntry.query.all() + + def __str__(self): + return self.name + + def to_dict(self): + return dict(id=self.id, name=self.name) + + def toJSON(self): + return json.dumps(self.to_dict(), default=lambda o: o.__dict__, + sort_keys=True, indent=4) + + +def pre_fill_table(): + a_es = {"url": "", } + access_entries = [AccessControlEntry(name=room['name'], number=room['room_number'], + building_name=room['building_name'], building_number=room['building_number']) for room in + a_es] + + try: + db.session.bulk_save_objects(access_entries) + db.session.commit() + except IntegrityError as e: + db.session.rollback() diff --git a/backend/models/user_model.py b/backend/models/user_model.py index a4a6b1b..1d6e47f 100644 --- a/backend/models/user_model.py +++ b/backend/models/user_model.py @@ -8,6 +8,7 @@ from sqlalchemy.orm import relation from sqlalchemy import MetaData from backend import db, app, login_manager +from backend.config import Config from backend.models.post_model import Post from backend.models.example_model import ExampleDataItem import re @@ -18,7 +19,6 @@ from datetime import datetime, timedelta from passlib.hash import sha256_crypt from hashlib import md5 - metadata = MetaData() followers = db.Table('followers', @@ -34,31 +34,30 @@ acquaintances = db.Table('acquaintances', # This is the association table for the many-to-many relationship between # groups and members - this is, the memberships. user_group_table = db.Table('user_group', - db.Column('user_id', db.Integer, - db.ForeignKey('user.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True), - db.Column('group_id', db.Integer, - db.ForeignKey('group.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True)) - + db.Column('user_id', db.Integer, + db.ForeignKey('user.id', + onupdate="CASCADE", + ondelete="CASCADE"), + primary_key=True), + db.Column('group_id', db.Integer, + db.ForeignKey('group.id', + onupdate="CASCADE", + ondelete="CASCADE"), + primary_key=True)) # This is the association table for the many-to-many relationship between # groups and permissions. group_permission_table = db.Table('group_permission', - db.Column('group_id', db.Integer, - db.ForeignKey('group.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True), - db.Column('permission_id', db.Integer, - db.ForeignKey('permission.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True)) + db.Column('group_id', db.Integer, + db.ForeignKey('group.id', + onupdate="CASCADE", + ondelete="CASCADE"), + primary_key=True), + db.Column('permission_id', db.Integer, + db.ForeignKey('permission.id', + onupdate="CASCADE", + ondelete="CASCADE"), + primary_key=True)) class User(UserMixin, db.Model): @@ -213,6 +212,16 @@ class User(UserMixin, db.Model): # TODO: implement correctly return True + @property + def effective_permissions(self): + permissions = Config.ROLE_PERMISSION_MAPPINGS.get(self.role, []) + for g in self.groups: + print(g) + for p in g.permissions: + print(p) + permissions.append(p) + return permissions + @staticmethod def decode_auth_token(auth_token): """ @@ -370,7 +379,7 @@ class User(UserMixin, db.Model): followers.c.follower_id == self.id).order_by(Post.timestamp.desc()) def to_dict(self): - #return self.__dict__ + # return self.__dict__ return dict(id=self.id, email=self.email, groups=[g.to_dict() for g in self.groups]) def toJSON(self): @@ -426,7 +435,6 @@ class Group(db.Model): def __init__(self, **kwargs): super(Group, self).__init__(**kwargs) - @staticmethod def get_by_name(name): """ @@ -461,7 +469,8 @@ class Permission(db.Model): name = db.Column(db.Unicode(63), unique=True, nullable=False) description = db.Column(db.Unicode(511)) groups = db.relationship(Group, secondary=group_permission_table, - back_populates='permissions') + back_populates='permissions') + access_control_entry = db.relationship('AccessControlEntry', back_populates='required_permission') @event.listens_for(User.__table__, 'after_create')