import sqlite3 from typing import List from sqlalchemy.engine import Engine from sqlalchemy import ForeignKey, event from sqlalchemy import Column, Integer, String, DateTime import datetime, decimal from db import dbauth SYS_ADMIN = "SysAdmin" APP_ADMIN = "AppAdmin" APP_USER = "User" APP_DOZENT = "Doz" BINDKEY = 'auth' #c = Connection() def columns2dict(record, excludefields=[]): if not isinstance(excludefields, list): excludefields = [] attribs = dict() if hasattr(record, '__table__'): if hasattr(record.__table__, 'columns'): for col in record.__table__.columns: if col.name in excludefields: continue value = getattr(record, col.name) if isinstance(value, datetime.date): value = value.isoformat() if isinstance(value, decimal.Decimal): value = "{0:.2f}".format(value) attribs[col.name] = value return attribs @event.listens_for(Engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): # Dieser Trigger darf nur bei einer Sqlite-Verbindung zünden. # Bei einer VErbindugn zum MS SQL-Server darf er nicht ausgelöst werden if isinstance(dbapi_connection, sqlite3.Connection): cursor = dbapi_connection.cursor() cursor.execute("PRAGMA foreign_keys=ON") cursor.close() class Systeminfo(dbauth.Model): __tablename__ = 'systeminfo' __bind_key__ = BINDKEY id = Column(Integer, primary_key=True) version = Column(String(20), nullable=False) lastupdate = Column(DateTime, nullable=True) initialinstall = Column(DateTime, nullable=True) # diese Tabelle enthält für alle Apps diejenigen Rollen, die in der App notwendig sind # Da die Rollen nur einmal in der Tabelle "Role" definiert sind und mehrere Apps die gleichen # Rollen haben können, handelt es sich im eine "many-to-many"-Relation, die in dieser # Hilfstabelle verwaltet werden apps_roles = dbauth.Table( 'apps_roles', Column('appid', Integer, ForeignKey('applications.id'), primary_key=True), Column('roleid', Integer, ForeignKey('roles.id'), primary_key=True), bind_key=BINDKEY ) # diese Tabelle enthält für alle Apps, in denen der User eine Rolle hat, die Rollen, # die der User in der jeweiligen App hat. # Da die Rollen nur einmal in der Tabelle "Role" definiert sind und verschiedene einem User # zugeordnete Apps die gleichen Rollen haben können, handelt es sich im eine # "many-to-many"-Relation, die in dieser Hilfstabelle verwaltet werden userapps_roles = dbauth.Table( 'userapps_roles', Column('userappid', Integer, ForeignKey('userapps.id'), primary_key=True), Column('roleid', Integer, ForeignKey('roles.id'), primary_key=True), bind_key=BINDKEY ) class Role(dbauth.Model): __tablename__ = 'roles' __bind_key__ = BINDKEY id = Column(Integer, primary_key=True) roleid = Column(String(80), index=True, unique=True, nullable=False, default=None) name = Column(String(80), nullable=False, default=None) level = Column(Integer, default=0) beschreibung = Column(String(800)) def to_dict(self): if self.level is None: self.level = 0 return columns2dict(self) # Die Tabelle "UserApp" enthält für jeden User für jede App, in der der User eine Rolle hat, # einen Datensatz. class Userapp(dbauth.Model): __tablename__ = 'userapps' __bind_key__ = BINDKEY id = Column(Integer, primary_key=True) userid = Column(ForeignKey('users.id'), index=True) appid = Column(ForeignKey('applications.id')) userapp_roles = dbauth.relationship('Role', secondary=userapps_roles, lazy='subquery', order_by="asc(Role.roleid)") application = dbauth.relationship('Application', uselist=False) def get_role(self, roleid: str) -> Role: """ Die Methode gibt die Rolle aus der userapp zurück, die mit der roleid (nicht id) angegeben wurde. Gibt es keine Rolle mit der übergebenen roleid, wird 'None' zurückgegeben - roleid: str - die roleid (nicht id) der Rollue, die gesucht wird """ for role in self.userapp_roles: if role.id == roleid: return role return None def to_dict(self, withrelations=True): result = columns2dict(self) result["userapp_roles"] = [] if withrelations: for userapprole in self.userapp_roles: result["userapp_roles"].append(userapprole.to_dict()) return result class User(dbauth.Model): __tablename__ = 'users' __bind_key__ = BINDKEY id = Column(Integer, primary_key=True) userid = Column(String(80), index=True, unique=True, nullable=False, default='') username = Column(String(80), nullable=False) password = Column(String(40), nullable=False) email = Column(String(120)) telefon = Column(String(800)) # Jeder User kann mehrere "User-Apps" haben. Dies sind die Apps, in denen dem User Rollen # zugewiesen wurden. Es handelt sich um eine "one-to-many"-Relation, da ein User zwar mehrere # User-Apps haben kann, jede User-App aber genau einem User gehört userapps = dbauth.relationship('Userapp') def get_userapp(self, appid:str) -> Userapp: """ Die Funktion liefert die userapp mit Hilfe der appid (nicht der id) zurück. Wenn der User keine userapp für die Application hat, wird 'None' zurückgegeben - appid: string - Der Kurzname der Application (nicht die id) """ for userapp in self.userapps: app = userapp.application if app.appid == appid: return userapp return None def get_userapp_by_id(self, id:int) -> Userapp: """ Die Funktion liefert die userapp mit Hilfe der id (nicht der appid) zurück - appid: int - die interne Record-ID der Application (nicht die userid) """ for userapp in self.userapps: app = userapp.application if app.id == id: return userapp return None def get_userapproles(self, appid:str) -> List[Role]: """ Die Funktion liefert die roles zurück, die der User in der durch die appid (nicht id) bestimmten Application hat - appid: str - Der Kurzname der Application """ userapp: Userapp = self.get_userapp(appid) if userapp is not None: return userapp.userapp_roles else: return [] def has_roles(self, roleids: List[str]) -> bool: """ Die Funktion liefert 'True' zurück, falls der User in irgendeiner Application eine der Rollen hat, deren roleids übergeben wurden, ansonsten 'False' - roleids: List[str] - eine Liste mit roleids (nicht ids) """ for userapp in self.userapps: for role in userapp.userapp_roles: if role.roleid in roleids: return True return False def is_SysAdmin(self) -> bool: """ die Methode liefert 'True' zurück, wenn der User in irgendeiner Anwendung SysAdmin ist """ return self.has_roles([SYS_ADMIN]) def __repr__(self): return '' % self.username def to_dict(self, withrelations=False): result = columns2dict(self, excludefields=['password']) result['userapps'] = [] if withrelations: for userapp in self.userapps: result['userapps'].append(userapp.to_dict()) return result def to_relationaldict(self): return self.to_dict(withrelations=True) def has_permission(self, appid:str, allowed_roles: List[str]): """ Gibt an, ob der User in einer bestimmten Anwendung eine der gewünschten Rollen hat - appid: string - Die appid (nicht id) der gewünschten Application - allowed_roles: List[str] - Eine Liste mit den roleids (nicht ids) der erlaubten Rollen """ userapp = self.get_userapp(appid) if userapp is not None: rolleerlaubt = any([role.roleid in allowed_roles for role in userapp.userapp_roles]) if rolleerlaubt: return True return False # Die nachfolgenden Properties werden für flask_login benötigt @property def is_authenticated(self): return True @property def is_active(self): return True @property def is_anonymous(self): return False def get_id(self): return self.userid class Application(dbauth.Model): __tablename__ = 'applications' __bind_key__ = BINDKEY id = Column(Integer, primary_key=True) appid = Column(String(80), index=True, unique=True, nullable=False, default=None) name = Column(String(80), nullable=False, default=None) minversion = Column(String(20)) beschreibung = Column(String(800)) roles = dbauth.relationship('Role', secondary=apps_roles, lazy='subquery', order_by="asc(Role.roleid)") def to_dict(self, withrelations=True): result = columns2dict(self) result['roles'] = [] for role in self.roles: result['roles'].append(role.to_dict()) return result print("########## auth/model importiert")