Commit 358d2f22 authored by Benjamin Fischer's avatar Benjamin Fischer
Browse files

[usermanagment] Implemented new controller needed for API-untangling.

parent 9d4347ef
......@@ -13,9 +13,216 @@ from vispa.models.workgroup import Workgroup, WorkgroupItem
import cherrypy
import json
from itertools import izip
from inspect import getargspec
from collections import deque
def DB():
return cherrypy.request.db
def call_mix(func, *args, **kwargs):
return func(*args, **{
arg: kwargs[arg]
for arg in getargspec(func)[0] if arg in kwargs
})
class Rights(int):
levels = [
"none",
"public",
"indirect",
"member",
"manager",
"admin",
]
def test(self, obj):
"""
Tests wether the object interaction is allowed with the given rights.
"""
user = cherrypy.request.user
fallback = (lambda: [user]) if user == obj else list
return self <= (
self.admin if user.serveradmin else
self.manager if getattr(obj, "get_managers", fallback)() else
self.member if user in getattr(obj, "get_users", fallback)() else
self.indirect if False else # TODO: implement this
self.public if getattr(self, "privacy", -1) == Group.PUBLIC else
self.none
)
@staticmethod
def extend(base, extension, delete=[]):
ret = {}
ret.update(base)
ret.update(extension)
for d in delete:
del ret[d]
return ret
for i, name in enumerate(Rights.levels):
setattr(Rights, name, Rights(i))
class BaseController(object):
def __init__(self, table, rights={}, assocs=[]):
self._table = table
self._rights = rights
for assoc in assocs:
setattr(self, assoc._assoc, assoc)
setattr(assoc, "_master", self)
def _rga(self, rights, *names):
# collect the object to work on
tar = self
tars = deque([tar])
for n in range(len(names) - 1):
tar = tar._master
tars.appendleft(tar)
rights = self._rights.get(rights, self._rights["__default__"])
if type(rights) is not list:
rights = [rights]
good = [True] * len(rights)
objs = deque()
for tar, name, right in izip(tars, names, izip(*rights)):
obj = tar._table.get_by_name(DB(), name)
if obj is None: # TODO: this is a info leak (about existence)
raise AjaxException(404)
good = [g and Rights(r).test(obj) for g, r in zip(good, right)]
if True not in good:
raise AjaxException(403)
objs.append(obj)
return objs
def _rt(self, rights):
rights = self._rights.get(rights, self._rights["__default__"])
if type(rights) is not list:
rights = [rights]
if not any(all(r.test(None) for r in right) for right in rights):
raise AjaxException(403)
def _rf(self, rights, obj_iter):
rights = self._rights.get(rights, self._rights["__default__"])
rights = Rights(rights[0] if type(rights) is tuple else rights)
if type(rights) is not list:
rights = [rights]
return [obj for obj in obj_iter if any(r.test(obj) for r in rights)]
# base tables
class TableController(BaseController):
"""
Basic controller for accessing items of a table.
"""
def __init__(self, *args, **kwargs):
self._setters = kwargs.pop("setters", [])
super(TableController, self).__init__(*args, **kwargs)
@cherrypy.expose
def create(self, name):
self._rt("create")
self._table.create(DB(), name)
@cherrypy.expose
def list(self):
return [entry.to_dict() for entry in self._rf("list", self._table.all(DB()))]
@cherrypy.expose
def info(self, name):
base, = self._rga("info", name)
return base.to_dict()
@cherrypy.expose
def rename(self, name, new_name):
base, = self._rga("rename", name)
base.rename(DB(), new_name)
@cherrypy.expose
def delete(self, name):
base, = self._rga("delete", name)
base.delete(DB())
@cherrypy.expose
def update(self, **kwargs):
for key, value in kwargs.items():
if key in self._setters:
base, = self._rgs("update_%s" % key, name)
func = getattr(base, "set_%s" % key)
func(value)
else:
raise AjaxException("Unknown property to updated: %s" % key, 404)
class AssociationController(BaseController):
def __init__(self, *args, **kwargs):
self._assoc = kwargs.pop("assoc", [])
super(AssociationController, self).__init__(*args, **kwargs)
@cherrypy.expose
def list(self, name):
base, = self._rga("list", name)
func = getattr(base, "get_%ss" % self._assoc)
return [obj.to_dict() for obj in call_mix(func, recursion_depth=0)]
@cherrypy.expose
def add(self, name, assoc_name):
base, target = self._rga("add", name, assoc_name)
func = getattr(base, "add_%s" % self._assoc)
func(DB(), target)
@cherrypy.expose
def remove(self, name, assoc_name):
base, target = self._rga("remove", name, assoc_name)
func = getattr(base, "remove_%s" % self._assoc)
func(DB(), target)
class AssociationToGroupController(AssociationController):
@cherrypy.expose
def add(self, name, assoc_name, password=""):
try:
super(AssociationToGroupController, self).add(name=name, assoc_name=assoc_name)
except AjaxException as e:
if e.code != 403:
raise e
# now try special cases
base, target = self._rga("add_self", name, assoc_name)
func = getattr(base, "add_%s" % self._assoc)
conft = Group_User_Assoc if self._table.__name__ == "User" else Group_Group_Assoc
if base.privacy == Group.PUBLIC:
func(DB(), target, conft.CONFIRMED)
elif base.privacy == Group.PROTECTED:
if not sha256_crypt.verify(password, base.password):
raise AjaxException(403)
func(DB(), target, conft.CONFIRMED)
elif base.privacy == Group.PRIVATE:
func(DB(), target, conft.UNCONFIRMED)
@cherrypy.expose
def confirm(self, name, assoc_name):
base, target = self._rga("confirm", name, assoc_name)
func = getattr(base, "confirm_%s" % self._assoc)
func(DB(), target)
class AssociationDeepController(AssociationController):
@cherrypy.expose
def list(self, name, mid_name):
base, mid, = self._rga("list", name)
func = getattr(base, "get_%ss" % self._assoc)
return [obj.to_dict() for obj in call_mix(func, mid, recursion_depth=0)]
@cherrypy.expose
def add(self, name, mid_name, assoc_name):
base, mid, target = self._rga("add", name, assoc_name)
func = getattr(base, "add_%s" % self._assoc)
func(DB(), mid, target)
@cherrypy.expose
def remove(self, name, mid_name, assoc_name):
base, mid, target = self._rga("remove", name, assoc_name)
func = getattr(base, "remove_%s" % self._assoc)
func(DB(), mid, target)
class UMAjaxController(AbstractController):
"""
TODO: UPDATE THIS
The UMAjaxController inherits all methods, which are neccessery for the user management. Most
of the function names are chosen self explanantory, while the first word corresponds to the
object of interest and the rest to the action performed on the object, e.g. user_get_groups
......@@ -25,6 +232,81 @@ class UMAjaxController(AbstractController):
properties.
"""
def __init__(self):
super(UMAjaxController, self).__init__()
# some commonly used rights
rights_admin = {"__default__": (Rights.admin,) * 3}
rights_manager = {"__default__": (Rights.manager, Rights.none)}
rights_assoc = {
"list": (Rights.member,),
"add": (Rights.manager, Rights.none),
"remove": [
(Rights.manager, Rights.none), # managers can kick members
(Rights.none, Rights.manager), # anyone can leave on their own
]
}
# now define the controllers
self.permission = TableController(table=Permission, rights=rights_admin)
self.role = TableController(table=Role, rights=rights_admin, assocs=[
AssociationController(table=Permission, assoc="permission", rights=rights_admin),
])
self.workgroup = TableController(table=Workgroup, rights={
"list": (Rights.member,),
"info": (Rights.member,),
"create": (Rights.none,),
"delete": (Rights.manager,),
"__default__": (Rights.manager,)
}, assocs=[
AssociationController(table=User, assoc="manager", rights=rights_manager),
AssociationController(table=User, assoc="user", rights=rights_assoc)
])
self.group = TableController(table=Group, rights={
"list": (Rights.public,),
"info": (Rights.member,),
"create": (Rights.admin,),
"delete": (Rights.admin,),
"update_status": (Rights.admin,),
"__default__": (Rights.manager,),
}, assocs=[
AssociationController(table=User, assoc="manager", rights=rights_manager),
] + [
AssociationToGroupController(table=t, assoc=a, rights=Rights.extend(rights_assoc, {
"list": (Rights.public,),
"add": (Rights.manager, Rights.none),
"add_self": (Rights.none, Rights.manager),
})) for t, a in [
(User, "user"),
(Group, "child_group")
]
], setters=[
"privacy",
"status",
"password",
])
self.project = TableController(table=Project, rights={
"list": (Rights.member,),
"info": (Rights.member,),
"create": (Rights.admin,),
"delete": (Rights.admin,),
"update_status": (Rights.admin,),
"__default__": (Rights.manager,),
}, assocs=[
AssociationController(table=User, assoc="manager", rights=rights_manager),
] + [
AssociationController(table=t, assoc=a, rights=rights_assoc, assocs=[
AssociationDeepController(table=Role, assoc="%s_role" % a, rights=rights_admin)
]) for t, a in [
(User, "user"),
(Group, "group")
]
], setters=[
"status"
])
@cherrypy.expose
def user_get_groups(self):
"""
......
......@@ -84,6 +84,16 @@ class Group(Base):
primaryjoin=id == Group_Group_Assoc.parent_group_id,
backref="parent_group")
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"created": self.created.isoformat(),
"privacy": self.privacy,
"password": not not self.password, # don't show this
"status": self.status,
}
@staticmethod
def get_by_name(session, name):
"""
......@@ -165,7 +175,7 @@ class Group(Base):
"""
return session.query(Group)
def delete(self):
def delete(self, session=None):
"""
Delete group. Internally, the delete flag is set, its not deleted from the database.
"""
......
......@@ -106,6 +106,16 @@ class Project(Base):
groups = relationship("Project_Group_Assoc", backref="project")
items = relationship("ProjectItem", backref="project")
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"created": self.created.isoformat(),
"status": self.status,
"password": not not self.password, # don't show this
"status": self.status,
}
@staticmethod
def get_by_id(session, gid):
"""
......@@ -172,7 +182,7 @@ class Project(Base):
if name is None:
raise Exception("Invalid projectname")
if Project.get_by_name(session, name) is not None:
raise Exception("Project %s already exists" %name)
raise Exception("Project %s already exists" % name)
# name valid -> create project
session.add(Project(name=name))
session.commit()
......@@ -228,7 +238,7 @@ class Project(Base):
def get_users(self):
"""
Returns users of project.
Returns users of project.
:returns: list of Project_User_Assoc objects
"""
......@@ -250,7 +260,7 @@ class Project(Base):
assoc = session.query(Project_User_Assoc).filter_by(user_id=user.id) \
.filter_by(project_id=self.id).first()
if assoc is not None:
raise Exception("User %s already in project %s" %(user.name, self.name))
raise Exception("User %s already in project %s" % (user.name, self.name))
assoc = Project_User_Assoc(project_id=self.id, user_id=user.id)
assoc.user = user
self.users.append(assoc)
......@@ -273,7 +283,7 @@ class Project(Base):
assoc = session.query(Project_User_Assoc).filter_by(user_id=user.id) \
.filter_by(project_id=self.id).first()
if assoc is None:
raise Exception("User %s not in project %s" %(user.name, self.name))
raise Exception("User %s not in project %s" % (user.name, self.name))
session.delete(assoc)
def get_groups(self):
......@@ -313,7 +323,7 @@ class Project(Base):
assoc = session.query(Project_Group_Assoc).filter_by(group_id=group.id) \
.filter_by(project_id=self.id).first()
if assoc is not None:
raise Exception("Group %s already in project %s" %(group.name, self.name))
raise Exception("Group %s already in project %s" % (group.name, self.name))
assoc = Project_Group_Assoc(project_id=self.id, group_id=group.id)
assoc.group = group
self.groups.append(assoc)
......@@ -330,13 +340,13 @@ class Project(Base):
:raises: TypeError if group is not instance of Group
:raises: Exception if group is not in project
"""
if not isinstance(group. vispa.models.group.Group):
if not isinstance(group, vispa.models.group.Group):
raise TypeError('invalid type of group')
# get Project_Group_Assoc object
assoc = session.query(Project_Group_Assoc).filter_by(group_id=group.id) \
.filter_by(project_id=self.id).first()
if assoc is None:
raise Exception("Group %s not in project %s" %(group.name, self.name))
raise Exception("Group %s not in project %s" % (group.name, self.name))
session.delete(assoc)
def get_managers(self):
......@@ -372,7 +382,7 @@ class Project(Base):
raise TypeError('invalid type of manager')
# is given manager manager of project?
if manager not in self.managers:
raise Exception("User %s is not manager of project %s" %(manager.name, self.name))
raise Exception("User %s is not manager of project %s" % (manager.name, self.name))
self.managers.remove(manager)
def get_roles_of_user(self, user):
......@@ -393,7 +403,7 @@ class Project(Base):
if x.user_id == user.id:
assoc = x
if assoc is None:
raise Exception("User %s not in project %s" %(user.name, self.name))
raise Exception("User %s not in project %s" % (user.name, self.name))
return assoc.roles
def get_roles_of_group(self, group):
......@@ -414,7 +424,7 @@ class Project(Base):
if x.group_id == group.id:
assoc = x
if assoc is None:
raise Exception("Group %s not in project %s" %(group.name, self.name))
raise Exception("Group %s not in project %s" % (group.name, self.name))
return assoc.roles
def set_roles_of_user(self, user, roles):
......@@ -440,7 +450,7 @@ class Project(Base):
if x.user_id == user.id:
assoc = x
if assoc is None:
raise Exception("User %s not in project %s" %(user.name, self.name))
raise Exception("User %s not in project %s" % (user.name, self.name))
assoc.roles = roles
def add_roles_to_user(self, user, roles):
......@@ -466,9 +476,63 @@ class Project(Base):
if x.user_id == user.id:
assoc = x
if assoc is None:
raise Exception("User %s not in project %s" %(user.name, self.name))
raise Exception("User %s not in project %s" % (user.name, self.name))
assoc.roles.extend(roles)
def get_user_roles(self, user):
return self.get_roles_of_user(user=user)
def add_user_role(self, user, role):
if not isinstance(user, User):
raise TypeError('invalid type of user')
if not isinstance(role, Role):
raise TypeError('Invalid type of roles')
for assoc in self.users:
if assoc.user_id == user.id:
if role in assoc.roles:
raise Exception("User %s has role %s already" % (user.name, role.name))
assoc.roles.append(role)
else:
raise Exception("User %s not in project %s" % (user.name, self.name))
def remove_user_role(self, user, role):
if not isinstance(user, User):
raise TypeError('invalid type of user')
if not isinstance(role, Role):
raise TypeError('Invalid type of roles')
for assoc in self.users:
if assoc.user_id == user.id:
assoc.roles.remove(role)
else:
raise Exception("User %s not in project %s" % (user.name, self.name))
def get_group_roles(self, group):
return self.get_roles_of_group(group=group)
def add_group_role(self, group, role):
if not isinstance(group, vispa.models.group.Group):
raise TypeError('invalid type of group')
if not isinstance(role, Role):
raise TypeError('Invalid type of roles')
for assoc in self.groups:
if assoc.group_id == group.id:
if role in assoc.roles:
raise Exception("Group %s has role %s already" % (group.name, role.name))
assoc.roles.append(role)
else:
raise Exception("Group %s not in project %s" % (group.name, self.name))
def remove_group_role(self, group, role):
if not isinstance(group, vispa.models.group.Group):
raise TypeError('invalid type of group')
if not isinstance(role, Role):
raise TypeError('Invalid type of roles')
for assoc in self.groups:
if assoc.group_id == group.id:
assoc.roles.remove(role)
else:
raise Exception("Group %s not in project %s" % (group.name, self.name))
def set_roles_of_group(self, group, roles):
"""
Sets the roles of a group in a project.
......@@ -492,7 +556,7 @@ class Project(Base):
if x.group_id == group.id:
assoc = x
if assoc is None:
raise Exception("Group %s not in project %s" %(group.name, self.name))
raise Exception("Group %s not in project %s" % (group.name, self.name))
assoc.roles = roles
def add_roles_to_group(self, group, roles):
......@@ -518,7 +582,7 @@ class Project(Base):
if x.group_id == group.id:
assoc = x
if assoc is None:
raise Exception("Group %s not in project %s" %(group.name, self.name))
raise Exception("Group %s not in project %s" % (group.name, self.name))
assoc.roles.extend(roles)
def get_items(self, itemtype=None):
......
......@@ -25,6 +25,13 @@ class Permission(Base):
name = Column(Unicode(255), nullable=False, unique=True)
created = Column(DateTime, nullable=False, default=datetime.now)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"created": self.created.isoformat()
}
@staticmethod
def get_by_id(session, gid):
"""
......@@ -145,6 +152,13 @@ class Role(Base):
created = Column(DateTime, nullable=False, default=datetime.now)
permissions = relationship("Permission", secondary=role_permission_association)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"created": self.created.isoformat(),
}
@staticmethod
def get_by_id(session, gid):
"""
......@@ -285,3 +299,16 @@ class Role(Base):
if not isinstance(permission, Permission):
raise TypeError('Invalid type of permission')
self.permissions = permissions
def get_permissions(self):
return self.permissions
def add_permission(self, permission):
self.add_permissions([permission])
def remove_permssion(self, permission):
if not isinstance(permission, Permission):
raise TypeError('Invalid type of permission')
if permission not in self.permissions:
raise Exception('Permission %s not in role %s' % (permission.name, self.name))
self.permission.remove(permission)
......@@ -35,6 +35,13 @@ class Workgroup(Base):
users = relationship("User", secondary=workgroup_user_association, backref="workgroups")
items = relationship("WorkgroupItem", backref="workgroup")
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"created": self.created.isoformat(),
}
@staticmethod
def get_by_id(session, gid):
"""
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment