From 3744b6d75d1df9ed78a5f01b3e43c15b3fa39e81 Mon Sep 17 00:00:00 2001 From: Christian Panton Date: Wed, 23 Jan 2013 10:05:54 +0100 Subject: [PATCH] backup --- goldmine/api/__init__.py | 2 +- goldmine/api/activity.py | 27 ++- goldmine/api/dataset/__init__.py | 51 ++++-- goldmine/api/dataset/sequence/__init__.py | 58 +++--- goldmine/api/dataset/type.py | 11 +- goldmine/api/favorite.py | 72 ++++++++ goldmine/api/group/__init__.py | 168 ++++++++++++++++++ goldmine/api/project.py | 10 +- goldmine/api/study.py | 145 ++++++++++++--- goldmine/api/user/__init__.py | 17 +- goldmine/api/user/permission.py | 72 ++++++++ goldmine/api/user/{settings.py => setting.py} | 0 goldmine/controller/__init__.py | 35 +++- goldmine/models/auth/__init__.py | 3 +- goldmine/models/auth/favorite.py | 31 ++++ goldmine/models/auth/group.py | 31 +++- goldmine/models/auth/permission.py | 16 +- goldmine/models/structure/study.py | 3 +- goldmine/protocols/json/__init__.py | 21 ++- goldmine/protocols/rest/__init__.py | 24 +-- schema.sql | 46 +++-- 21 files changed, 700 insertions(+), 143 deletions(-) create mode 100644 goldmine/api/favorite.py create mode 100644 goldmine/api/group/__init__.py create mode 100644 goldmine/api/user/permission.py rename goldmine/api/user/{settings.py => setting.py} (100%) create mode 100644 goldmine/models/auth/favorite.py diff --git a/goldmine/api/__init__.py b/goldmine/api/__init__.py index 8003b38..88fa766 100644 --- a/goldmine/api/__init__.py +++ b/goldmine/api/__init__.py @@ -6,7 +6,7 @@ from goldmine.models import * from goldmine.controller import * -@apimethod(username="string", password="string") +@apimethod def authenticate(username, password): """ Authenticate a user diff --git a/goldmine/api/activity.py b/goldmine/api/activity.py index af88e34..4d8730d 100644 --- a/goldmine/api/activity.py +++ b/goldmine/api/activity.py @@ -4,16 +4,26 @@ """ Activity functions """ +from storm.locals import * + from goldmine import * from goldmine.db import db from goldmine.models import * from goldmine.controller import * -@apimethod.auth(activity_id="uuid") +@apimethod.auth def get(activity_id): - - activity_id = uuid(activity_id) - return not_empty(db().get(structure.Activity, activity_id)) + activity_id = uuid(activity_id, user) + activity = not_empty(db().get(structure.Activity, activity_id)).serialize() + + # limit access to studies + studies = [] + for study in activity["studies"]: + if resolver.get("study.access", user)(study["id"], min_role="read"): + studies.append(study) + + activity["studies"] = studies + return activity @apimethod.auth def all(project=None): @@ -22,10 +32,13 @@ def all(project=None): @apimethod.auth def search(keyword): - rs = db().find(structure.Activity, structure.Activity.name == keyword).order_by(structure.Activity.name) #FIXME like search + dscr + + keyword = "%%%s%%" % keyword + rs = db().find(structure.Activity, Or(structure.Activity.name.like(keyword), structure.Activity.description.like(keyword))) + rs = rs.order_by(structure.Activity.name) return rs_to_list(rs) -@apimethod.auth("activity.create", project_id="uuid", location="location") +@apimethod.auth("activity.create") def create(project_id, name, description=None, location={}): activity = structure.Activity() @@ -33,7 +46,7 @@ def create(project_id, name, description=None, location={}): activity.name = name activity.description = description - if len(location) : + if len(location): loc = structure.Location.from_struct(location) activity.location = db().add(loc) diff --git a/goldmine/api/dataset/__init__.py b/goldmine/api/dataset/__init__.py index bab4e31..49e8601 100644 --- a/goldmine/api/dataset/__init__.py +++ b/goldmine/api/dataset/__init__.py @@ -14,39 +14,52 @@ @apimethod.auth def get(dataset_id): - #FIXME: User has access? - dataset_id = uuid(dataset_id) + dataset_id = uuid(dataset_id, user) ds = not_empty(db().get(dataset.Dataset, dataset_id)) + check_access(user, ds.study_id, "read") return ds @apimethod.auth def fork(from_dataset_id, to_dataset_id, fork_type="derived"): - #FIXME: User has access? #FIXME: Check for circular graph - from_dataset_id = uuid(from_dataset_id) - to_dataset_id = uuid(to_dataset_id) + from_dataset_id = uuid(from_dataset_id, user) + to_dataset_id = uuid(to_dataset_id, user) from_dataset = not_empty(db().get(Dataset, from_dataset_id)) to_dataset = not_empty(db().get(Dataset, to_dataset_id)) + check_access(user, from_dataset.study_id, "read") + check_access(user, to_dataset.study_id, "write") + # FIXME: pass on to private function, what happens to user? return do_fork(from_dataset, to_dataset, user) -@apimethod.auth -def close(dataset_id): - #FIXME: User has access? - dataset_id = uuid(dataset_id) +@apimethod.auth("dataset.close") +def close(dataset_id): + + # Fixme: only owner should be able to close + + dataset_id = uuid(dataset_id, user) ds = not_empty(db().get(dataset.Dataset, dataset_id)) + + check_access(user, ds.study_id, "write") + if ds.closed is None: ds.closed = datetime.datetime.now() else: raise Exception("Dataset already closed") -@apimethod.auth +@apimethod.auth("dataset.purge") def purge(dataset_id): - #FIXME: User has access? - dataset_id = uuid(dataset_id) + + # Fixme: only owner should be able to purge? + + dataset_id = uuid(dataset_id, user) ds = not_empty(db().get(dataset.Dataset, dataset_id)) + + check_access(user, ds.study_id, "write") + + if ds.closed is None: db().remove(ds) #FIXME: cascade @@ -72,10 +85,11 @@ def do_fork(from_dataset, to_dataset, user, fork_type="derived"): def create(type, study, description, dataset_forked_from=None, fork_type="derived"): - #FIXME: User has access? + + check_access(user, study.id, "write") - s = resolver.get("dataset.supported_dataset_types", user) - if type not in s(): + supported = resolver.get("dataset.supported_dataset_types", user)() + if type not in supported: raise Exception("Unsupported type") ds = dataset.Dataset() @@ -86,7 +100,7 @@ def create(type, study, description, dataset_forked_from=None, fork_type="derive ds = db().add(ds) if dataset_forked_from is not None: - from_dataset_id = uuid(dataset_forked_from) + from_dataset_id = uuid(dataset_forked_from, user) from_dataset = not_empty(db().get(dataset.Dataset, from_dataset_id)) #FIXME do_fork(from_dataset, ds, user, fork_type) @@ -94,3 +108,8 @@ def create(type, study, description, dataset_forked_from=None, fork_type="derive return ds +def check_access(user, study_id, role, throw=True): + if not resolver.get("study.access", user)(study_id, min_role=role): + if throw: raise UnauthorizedException("You are not authorized to view this dataset") + return False + return True \ No newline at end of file diff --git a/goldmine/api/dataset/sequence/__init__.py b/goldmine/api/dataset/sequence/__init__.py index 9341aba..909a7a8 100644 --- a/goldmine/api/dataset/sequence/__init__.py +++ b/goldmine/api/dataset/sequence/__init__.py @@ -15,19 +15,22 @@ @apimethod.auth def get(dataset_id): - #FIXME: User has access? - return sequence_from_dataset(dataset_id) - + sequence = sequence_from_dataset(dataset_id, user) + check_access(user, sequence.dataset.study_id, "read") + return sequence + @apimethod.auth("dataset.sequence.create") def create(study_id, description, index_type_id, index_marker_type="point", index_marker_location="center", dataset_forked_from=None): - study_id = uuid(study_id) - index_type_id = uuid(index_type_id) + study_id = uuid(study_id, user) + check_access(user, study_id, "write") + + index_type_id = uuid(index_type_id, user) study = not_empty(db().get(structure.Study, study_id)) index_type = not_empty(db().get(dataset.Type, index_type_id)) - #FIXME unclean - + + #FIXME unclean parent = resolver.get("dataset.create", user)(u"sequence", study, description, dataset_forked_from) sequence = dataset.sequence.Sequence() @@ -41,13 +44,14 @@ def create(study_id, description, index_type_id, index_marker_type="point", inde @apimethod.auth def add_parameter(dataset_id, type_id, uncertainty_value=None, uncertainty_type="absolute", storage="float"): - #FIXME: User has access? - sequence = sequence_from_dataset(dataset_id) - + + sequence = sequence_from_dataset(dataset_id, user) + check_access(user, sequence.dataset.study_id, "write") + if sequence.dataset.closed: raise Exception("Dataset is closed") - type_id = uuid(type_id) + type_id = uuid(type_id, user) type = not_empty(db().get(dataset.Type, type_id)) param = dataset.sequence.Parameter() @@ -86,13 +90,12 @@ def add_data(dataset_id, index, parameter_id, value, uncertainty = None, uncerta for corresponding parameter ids """ - sequence = sequence_from_dataset(dataset_id) - + sequence = sequence_from_dataset(dataset_id, user) + check_access(user, sequence.dataset.study_id, "write") + if sequence.dataset.closed: raise Exception("Dataset is closed") - - # FIXME user has access? - + if type(index) in [tuple, list]: if sequence.index_marker_type != "span": @@ -165,11 +168,10 @@ def get_data(dataset_id, parameter_id=None, limit_min=None, limit_max=None): limit_max: not set - no upper index limit int, float - upper index limit specified by number """ - - #FIXME user has access - - sequence = sequence_from_dataset(dataset_id) + sequence = sequence_from_dataset(dataset_id, user) + check_access(user, sequence.dataset.study_id, "read") + has_span = sequence.index_marker_type == "span" if parameter_id is None: @@ -276,16 +278,26 @@ def get_data(dataset_id, parameter_id=None, limit_min=None, limit_max=None): @apimethod.auth def add_metadata(dataset_id, parameter_id=None, index_id=None, datapoint_id=None): - sequence = sequence_from_dataset(dataset_id) + sequence = sequence_from_dataset(dataset_id, user) + check_access(user, sequence.dataset.study_id, "write") + # FIXME missing implementation pass # UTIL -def sequence_from_dataset(dataset_id): - dataset_id = uuid(dataset_id) +def sequence_from_dataset(dataset_id, user): + dataset_id = uuid(dataset_id, user) return not_empty(db().find(dataset.sequence.Sequence, dataset.sequence.Sequence.dataset_id == dataset_id).one()) + +def check_access(user, study_id, role, throw=True): + if not resolver.get("study.access", user)(study_id, min_role=role): + if throw: raise UnauthorizedException("You are not authorized to view this sequence") + return False + return True + + """ @needauth diff --git a/goldmine/api/dataset/type.py b/goldmine/api/dataset/type.py index 6d483ba..fa7144a 100644 --- a/goldmine/api/dataset/type.py +++ b/goldmine/api/dataset/type.py @@ -5,6 +5,7 @@ """ Dataset.Type functions """ +from storm.locals import * from goldmine import * from goldmine.db import db @@ -12,9 +13,9 @@ from goldmine.controller import * -@apimethod +@apimethod.auth def get(type_id): - type_id = uuid(type_id) + type_id = uuid(type_id, user) return not_empty(db().get(dataset.Type, type_id)) @@ -39,5 +40,7 @@ def all(): @apimethod def search(keyword): - rs = db().find(dataset.Type, dataset.Type.name == keyword).order_by(dataset.Type.name) - return rs_to_list(rs) + keyword = "%%%s%%" % keyword + rs = db().find(dataset.Type, Or(dataset.Type.name.like(keyword), dataset.Type.description.like(keyword))) + rs = rs.order_by(dataset.Type.name) + return rs_to_list(rs) \ No newline at end of file diff --git a/goldmine/api/favorite.py b/goldmine/api/favorite.py new file mode 100644 index 0000000..66d4650 --- /dev/null +++ b/goldmine/api/favorite.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +""" +Favorite functions +""" + +import string + +from storm.locals import * + +from goldmine import * +from goldmine.db import db +from goldmine.models import * +from goldmine.controller import * + +@apimethod.auth +def get(name): + rs = db().find(auth.Favorite, auth.Favorite.name == name, auth.Favorite.user == user) + return not_empty(rs.one()) + +@apimethod.auth +def get_by_reference(id, type): + id = uuid(id) + rs = db().find(auth.Favorite, + auth.Favorite.ref_id == id, + auth.Favorite.ref_type == type, + auth.Favorite.user == user) + return rs.one() + +@apimethod.auth +def all(): + rs = db().find(auth.Favorite, auth.Favorite.user == user).order_by(auth.Favorite.name) + return rs_to_list(rs) + +@apimethod.auth +def add(name, reference_id, reference_type="dataset"): + + if reference_type not in auth.Favorite.REFMAP: + raise TypeError("Invalid reference type") + + if name != valid_name(name): + raise TypeError("Invalid name, no spaces or special characters!") + + favorite = auth.Favorite() + favorite.name = name + favorite.user = user + favorite.ref_id = uuid(reference_id) + favorite.ref_type = reference_type + + return db().add(favorite) + + +@apimethod.auth +def remove(name): + rs = db().find(auth.Favorite, auth.Favorite.name == name, auth.Favorite.user == user) + favorite = not_empty(rs.one()) + + if favorite.user == user: + db().remove(favorite) + else: + raise TypeError("Not your favorite!") + +@apimethod.auth +def resolve(name): + rs = db().find(auth.Favorite, auth.Favorite.name == name, auth.Favorite.user == user) + favorite = not_empty(rs.one()) + return unicode(favorite.ref_id) + +def valid_name(name): + valid_chars = "-_.%s%s" % (string.ascii_letters, string.digits) + return ''.join(c for c in name if c in valid_chars) \ No newline at end of file diff --git a/goldmine/api/group/__init__.py b/goldmine/api/group/__init__.py new file mode 100644 index 0000000..4251029 --- /dev/null +++ b/goldmine/api/group/__init__.py @@ -0,0 +1,168 @@ +""" +Group functions +""" + +from goldmine import * +from goldmine.db import db +from goldmine.models import * +from goldmine.controller import * + +@apimethod.auth +def get(group_id): + """ Get the requested group struct """ + group_id = uuid(group_id, user) + return db().get(auth.Group, group_id) + +@apimethod.auth +def all(): + rs = db().find(auth.Group).order_by(auth.Group.name) + return rs_to_list(rs) + +@apimethod.auth("group.create") +def create(name, parent_id=None): + u = auth.Group() + u.name = name + + if parent_id: + u.parent_id = uuid(parent_id) + + return db().add(u) + +@apimethod.auth +def add_member(group_id, user_id): + """ + Add user to group + """ + + group_id = uuid(group_id, user) + user_id = uuid(user_id, user) + + # FIXME: security + + gm = auth.GroupMember() + gm.user_id = user_id + gm.group_id = group_id + + return db().add(gm) + +@apimethod.auth +def remove_member(group_id, user_id): + """ + Remove user from group, but it cannot be yourself (unless you are an admin) + """ + + group_id = uuid(group_id, user) + user_id = uuid(user_id, user) + + # FIXME: security + + rs = db().find(auth.GroupMember, auth.GroupMember.group_id == group_id, auth.GroupMember.user_id == user_id) + gm = rs.one() + + if gm is None: + raise TypeError("Group membership not found") + + if gm.user == user and not user.is_admin(): + raise TypeError("You cannot remove yourself from a group") + + db().remove(gm) + + + + +@apimethod.auth +def memberships(user_id=None, explicit=False): + + """ + List group memberships for user + + user_id: user_id of queried user + None for current user + + explicit: List only explicit memberships + + """ + + if user_id: + user_id = uuid(user_id, user) + else: + user_id = user.id + + memberships = set() + rs = db().find(auth.GroupMember, auth.GroupMember.user_id == user_id) + + for group in rs: + memberships.add(group.group) + + if explicit: + return list(memberships) + + implicit = set() + for ms in memberships: + while ms.parent: + implicit.add(ms.parent) + ms = ms.parent + + return rs_to_list(memberships.union(implicit)) + + +@apimethod.auth +def has_member(group_id, user_id=None, explicit=False): + + """ + Is user a member of group + + group_id: group_id of queried group + + user_id: user_id of queried user + None for current user + + explicit: only explicit memberships + """ + + group_id = uuid(group_id, user) + group = db().get(auth.Group, group_id) + + if user_id: + user_id = uuid(user_id, user) + else: + user_id = user.id + + for member in group.members: + if member.id == user_id: return True + + if explicit: + return False + else: + return _implicit_member(group, user_id) + + +def _implicit_member(group, user_id): + """ + Find if user is an implicit member of group + """ + for child in group.children: + for member in child.members: + if member.id == user_id: return True + if _implicit_member(child, user_id): + return True + return False + + +@apimethod.auth +def tree(): + """ Get a tree of all groups """ + groups = db().find(auth.Group, auth.Group.parent_id == None) + objs = [] + for group in groups: + obj = _subtree(group) + objs.append(obj) + + return objs + +def _subtree(group): + obj = {} + obj["id"] = unicode(group.id) + obj["name"] = group.name + obj["children"] = map(_subtree, group.children) + return obj \ No newline at end of file diff --git a/goldmine/api/project.py b/goldmine/api/project.py index fe516f0..26ead75 100644 --- a/goldmine/api/project.py +++ b/goldmine/api/project.py @@ -4,6 +4,7 @@ """ Project functions """ +from storm.locals import * from goldmine import * from goldmine.db import db @@ -12,8 +13,7 @@ @apimethod.auth def get(project_id): - project_id = uuid(project_id) - + project_id = uuid(project_id, user) return not_empty(db().get(structure.Project, project_id)) @apimethod.auth @@ -23,10 +23,12 @@ def all(): @apimethod.auth def search(keyword): - rs = db().find(structure.Project, structure.Project.name == keyword).order_by(structure.Project.name) + keyword = "%%%s%%" % keyword + rs = db().find(structure.Project, Or(structure.Project.name.like(keyword), structure.Project.description.like(keyword))) + rs = rs.order_by(structure.Project.name) return rs_to_list(rs) -@apimethod.auth("project.create", location="location") +@apimethod.auth("project.create") def create(name, description=None, location={}): project = structure.Project() diff --git a/goldmine/api/study.py b/goldmine/api/study.py index 558004d..61e6c05 100644 --- a/goldmine/api/study.py +++ b/goldmine/api/study.py @@ -15,8 +15,10 @@ @apimethod.auth def get(study_id): - #FIXME: does user have access? - study_id = uuid(study_id) + + check_access(user, study_id, "read") + + study_id = uuid(study_id, user) return not_empty(db().get(structure.Study, study_id)) @apimethod.auth("study.create") @@ -28,34 +30,40 @@ def create(name, description=None): return db().add(s) @apimethod.auth -def all(standalone=False, owned_by_user=False): - #FIXME: does user have access? - if standalone: - subselect = Select(structure.ActivityStudy.study_id, distinct=True) - if owned_by_user: - rs = db().find(structure.Study, And(Not(structure.Study.id.is_in(subselect)), structure.Study.owner == user)) - else: - rs = db().find(structure.Study, Not(structure.Study.id.is_in(subselect))) +def all(owned_by_user=False): + + if owned_by_user: + # owned == access + rs = db().find(structure.Study, structure.Study.owner == user) + rs = rs.order_by(structure.Study.name) + return rs_to_list(rs) + else: - if owned_by_user: - rs = db().find(structure.Study, structure.Study.owner == user) - else: - rs = db().find(structure.Study) + rs = db().find(structure.Study) + rs = rs.order_by(structure.Study.name) + result = [] + for study in rs: + if check_access(user, study.id, "read", throw=False): + result.append(study) + return rs_to_list(result) - rs = rs.order_by(structure.Study.name) - return rs_to_list(rs) @apimethod.auth def search(keyword): - #FIXME: does user have access? - rs = db().find(structure.Study, structure.Study.name == keyword) #FIXME like search + descr - return rs_to_list(rs) + keyword = "%%%s%%" % keyword + rs = db().find(structure.Study, Or(structure.Study.name.like(keyword), structure.Study.description.like(keyword))) + result = [] + for study in rs: + if check_access(user, study.id, "read", throw=False): + result.append(study) + return rs_to_list(result) @apimethod.auth def lineage(study_id): - #FIXME: does user have access? - study_id = uuid(study_id) + check_access(user, study_id, "read") + + study_id = uuid(study_id, user) study = not_empty(db().get(structure.Study, study_id)) edges = [] @@ -81,14 +89,95 @@ def lineage(study_id): @apimethod.auth def add_activity(study_id, activity_id): - # FIXME: does user have access? - study_id = uuid(study_id) - activity_id = uuid(activity_id) + check_access(user, study_id, "admin") + + study_id = uuid(study_id, user) + activity_id = uuid(activity_id, user) study = not_empty(db().get(structure.Study, study_id)) activity = not_empty(db().get(structure.Activity, activity_id)) activity.studies.add(study) + +@apimethod.auth +def add_group(study_id, group_id, role): + """ + Give group access to study + """ + + check_access(user, study_id, "admin") + + study_id = uuid(study_id, user) + group_id = uuid(group_id, user) + + # FIXME: check that ids are present in referenced sets + sg = auth.StudyGroup() + sg.study_id = study_id + sg.group_id = group_id + sg.role = role + + return db().add(sg) + +@apimethod.auth +def remove_group(study_id, group_id): + """ + Remove group access to study + """ + + check_access(user, study_id, "admin") + + study_id = uuid(study_id, user) + group_id = uuid(group_id, user) + + rs = db().find(auth.StudyGroup, auth.StudyGroup.study_id == study_id, auth.StudyGroup.group_id == group_id).one() + + if rs: + db().remove(rs) + else: + raise TypeError("Group access not found") + +@apimethod.auth +def access(study_id, user_id=None, min_role="read"): + """ + Checks if user has access to study + + study_id: the study to check + user_id: the user to check + None for current user + min_role: the minimal role requested + """ + + #FIXME investigate caching + + study_id = uuid(study_id, user) + user_id = uuid(user_id, user) + + try: + min_role = auth.StudyGroup.ROLEMAP[min_role] + except KeyError: + raise TypeError("Invalid role '%s'" % min_role) + + if user_id is None: + user_id = user.id + + the_user = not_empty(db().get(auth.User, user_id)) + + if the_user.is_admin(): + return True + + study = not_empty(db().get(structure.Study, study_id)) + + if the_user == study.owner: + return True + + checker = resolver.get("group.has_member", user) + + for sg in study.access: + if sg.ROLEMAP[sg.role] >= min_role: # requested role available in group + if checker(sg.group_id, user_id): # check for user is member of group + return True + return False + ######## PRIVATE STUFF ############### def generate_lineage_tree(study, node, parent=None, level=0): @@ -121,6 +210,8 @@ def generate_lineage_tree(study, node, parent=None, level=0): return (edges, info) - - - +def check_access(user, study_id, role, throw=True): + if not resolver.get("study.access", user)(study_id, min_role=role): + if throw: raise UnauthorizedException("You are not authorized to view this study") + return False + return True \ No newline at end of file diff --git a/goldmine/api/user/__init__.py b/goldmine/api/user/__init__.py index 2389463..03b21cb 100644 --- a/goldmine/api/user/__init__.py +++ b/goldmine/api/user/__init__.py @@ -10,11 +10,17 @@ from goldmine.models import * from goldmine.controller import * -@apimethod.auth(who="uuid") -def get(who): - """ Get the requested user struct """ - who = uuid(who) - return db().get(auth.User, who) +@apimethod.auth +def get(user_id): + """ Get user struct for user_id """ + user_id = uuid(user_id, user) + return db().get(auth.User, user_id) + +@apimethod.auth +def all(): + """ All users """ + rs = db().find(auth.User) + return rs_to_list(rs) @apimethod.auth def whoami(): @@ -33,4 +39,3 @@ def create(username, fullname, email, password): u.set_password(password) return db().add(u) - diff --git a/goldmine/api/user/permission.py b/goldmine/api/user/permission.py new file mode 100644 index 0000000..469634e --- /dev/null +++ b/goldmine/api/user/permission.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +""" +User Permissions functions +""" + +from goldmine import * +from goldmine.db import db +from goldmine.models import * +from goldmine.controller import * + + +@apimethod.auth("user.permission.grant") +def grant(user_id, identifier): + """ Grant a permission """ + user_id = uuid(user_id, user) + p = auth.Permission() + p.user_id = user_id + p.identifier = identifier + p.granted_by = user + return db().add(p) + + +@apimethod.auth("user.permission.grant") +def revoke(user_id, identifier=None): + """ + Revoke a permission + + user_id: the id of the user to revoke permissions for + identifier: the permission as a string + None to strip all permissions from user + + returns the number of revoked permissions + """ + user_id = uuid(user_id, user) + + if identifier: + revoked = db().find(auth.Permission, auth.Permission.user_id == user_id, auth.Permission.identifier == identifier) + else: + revoked = db().find(auth.Permission, auth.Permission.user_id == user_id) + + for perm in revoked: + db().remove(perm) + return len(list(revoked)) + +@apimethod.auth +def access(identifier, user_id=None): + """ + Check if user has access to permission + """ + + user_id = uuid(user_id, user) + + if not user_id: + user_id = user.id + + result = db().find(auth.Permission, auth.Permission.user_id == user_id and auth.Permission.identifier == identifier) + return result.one() is not None + +@apimethod.auth +def all(): + """ Get all permissions issued for current user """ + perms = db().find(auth.Permission, auth.Permission.user == user) + return rs_to_list(perms) + +@apimethod.auth +def all_for(user_id): + """ Get all permissions issued for a specific user """ + user_id = uuid(user_id, user) + perms = db().find(auth.Permission, auth.Permission.user_id == user_id) + return rs_to_list(perms) \ No newline at end of file diff --git a/goldmine/api/user/settings.py b/goldmine/api/user/setting.py similarity index 100% rename from goldmine/api/user/settings.py rename to goldmine/api/user/setting.py diff --git a/goldmine/controller/__init__.py b/goldmine/controller/__init__.py index 7e379ac..3bc11aa 100644 --- a/goldmine/controller/__init__.py +++ b/goldmine/controller/__init__.py @@ -23,7 +23,6 @@ class InvalidRequest(Exception): class apimethod: def __init__(self, method=None, *args, **kwargs): - self.method = method self.auth_required = False self.permission = None @@ -59,14 +58,18 @@ def token(self, token): return self.as_user(user) def check_access(self, user=None): - + if (self.auth_required or self.permission) and user is None: raise UnauthorizedException("Method requires authenticated user") + if user and user.is_admin(): + return + if self.permission: - if user.is_admin(): - return - print "FIXME: PERMISSION ", + for perm in self.permission: + if not _get("user.permission.access", user)(unicode(perm)): + raise UnauthorizedException("Method requires permission: %s" % perm) + def __call__(self, *args, **kwargs): @@ -105,14 +108,30 @@ def default(val, defaultval): else: return val -def uuid(s): +def uuid(s, user=None): + + if type(s) == _uuid.UUID: + return s + if s is None: return None + + if isinstance(s, unicode) and s.startswith("#"): + + if user is None: + raise TypeError("Favorite is not availiable for this request") + + s = s[1:] + s = _get("favorite.resolve", user)(s) + + if not s: + raise TypeError("Unknown favorite") + try: - return _uuid.UUID(s) + return _uuid.UUID(s) except: raise TypeError("Malformed UUID") # import into namespace -from goldmine.controller.resolver import Resolver +from goldmine.controller.resolver import Resolver, get as _get from goldmine.controller.controller import Controller diff --git a/goldmine/models/auth/__init__.py b/goldmine/models/auth/__init__.py index 3833ebf..9644c54 100644 --- a/goldmine/models/auth/__init__.py +++ b/goldmine/models/auth/__init__.py @@ -3,7 +3,8 @@ from goldmine.models.auth.token import Token from goldmine.models.auth.user import User -from goldmine.models.auth.group import Group, GroupMember +from goldmine.models.auth.group import Group, GroupMember, StudyGroup from goldmine.models.auth.permission import Permission +from goldmine.models.auth.favorite import Favorite diff --git a/goldmine/models/auth/favorite.py b/goldmine/models/auth/favorite.py new file mode 100644 index 0000000..bc07bcb --- /dev/null +++ b/goldmine/models/auth/favorite.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +from storm.locals import * +from goldmine.models import * +from goldmine.db import generate_uuid + +class Favorite(Model): + + __storm_table__ = "favorite" + __export__ = ["name", ("user", "user_id"), "ref_id", "ref_type"] + __module__ = "goldmine.models.auth" + + REFMAP = { + "dataset": 1, + "datatype": 2, + "study": 3, + "activity": 4, + "project": 5, + "user": 6, + "group": 7 + } + + id = UUID(primary=True, default_factory=generate_uuid) + name = Unicode() + + user_id = UUID() + ref_id = UUID() + ref_type = Enum(map=REFMAP) + + user = Reference(user_id, "auth.User.id") diff --git a/goldmine/models/auth/group.py b/goldmine/models/auth/group.py index 9f66072..7b59f8d 100644 --- a/goldmine/models/auth/group.py +++ b/goldmine/models/auth/group.py @@ -7,12 +7,15 @@ class Group(Model): - __export__ = ["id", ("parent", "parent_id"), "name"] + __storm_table__ = "group" + __export__ = ["id", ("parent", "parent_id"), ("children", None), ("members", None), "name"] __module__ = "goldmine.models.auth" id = UUID(primary=True, default_factory=generate_uuid) parent_id = UUID() parent = Reference(parent_id, "auth.Group.id") + children = ReferenceSet(id, "auth.Group.parent_id") + members = ReferenceSet(id, "auth.GroupMember.group_id", "auth.GroupMember.user_id", "auth.User.id") name = Unicode() @@ -21,10 +24,32 @@ class GroupMember(Model): __storm_table__ = "group_member" __storm_primary__ = "user_id", "group_id" __module__ = "goldmine.models.auth" - + __export__ = [("user", "user_id"), ("group", "group_id")] + + user_id = UUID() group_id = UUID() user = Reference(user_id, "auth.User.id") - group = Reference(user_id, "auth.Group.id") + group = Reference(group_id, "auth.Group.id") + + + +class StudyGroup(Model): + + __storm_table__ = "study_group" + __module__ = "goldmine.models.auth" + __storm_primary__ = "study_id", "group_id" + + __export__ = [("study", "study_id"), ("group", "group_id"), "role"] + + ROLEMAP = {"read": 1, "write": 2, "admin": 3} + study_id = UUID() + group_id = UUID() + role = Enum(map=ROLEMAP) + + study = Reference(study_id, "structure.Study.id") + group = Reference(group_id, "auth.Group.id") + + diff --git a/goldmine/models/auth/permission.py b/goldmine/models/auth/permission.py index 5f934fd..e85f149 100644 --- a/goldmine/models/auth/permission.py +++ b/goldmine/models/auth/permission.py @@ -1,18 +1,24 @@ #!/usr/bin/env python #-*- coding:utf-8 -*- +import datetime + from storm.locals import * from goldmine.models import * from goldmine.db import generate_uuid class Permission(Model): - __export__ = ["id", ("study", "study_id"), ("group", "group_id"), "name", "identifier"] + __storm_table__ = "permission" + __export__ = [("user", "user_id"), ("granted_by", "granted_by_id"), "granted", "identifier"] __module__ = "goldmine.models.auth" + __storm_primary__ = "user_id", "identifier" - study_id = UUID() - group_id = UUID() + user_id = UUID() + granted_by_id = UUID() + granted = DateTime(default_factory=datetime.datetime.now) identifier = Unicode() - study = Reference(study_id, "structure.Study.id") - group = Reference(group_id, "auth.Group.id") + user = Reference(user_id, "auth.User.id") + granted_by = Reference(granted_by_id, "auth.User.id") + diff --git a/goldmine/models/structure/study.py b/goldmine/models/structure/study.py index aeeec84..e258b65 100644 --- a/goldmine/models/structure/study.py +++ b/goldmine/models/structure/study.py @@ -8,7 +8,7 @@ class Study(Model): __storm_table__ = "study" - __export__ = ["id", "name", "description", ("activities", None), ("datasets", None), ("owner", "owner_id")] + __export__ = ["id", "name", "description", ("activities", None), ("datasets", None), ("owner", "owner_id"), ("access", None)] __module__ = "goldmine.models.structure" id = UUID(primary=True, default_factory=generate_uuid) @@ -19,3 +19,4 @@ class Study(Model): activities = ReferenceSet(id, "structure.ActivityStudy.study_id", "structure.ActivityStudy.activity_id", "structure.Activity.id") datasets = ReferenceSet(id, "dataset.Dataset.study_id") owner = Reference(owner_id, "auth.User.id") + access = ReferenceSet(id, "auth.StudyGroup.study_id") diff --git a/goldmine/protocols/json/__init__.py b/goldmine/protocols/json/__init__.py index 43efd71..525c9a9 100644 --- a/goldmine/protocols/json/__init__.py +++ b/goldmine/protocols/json/__init__.py @@ -63,11 +63,10 @@ def handleRequest(self, data, env, shortpath): # resolve method try: resolved_method = ctrl.get_method(method) - except controller.MethodNotFoundException: - raise JSONRPCMethodNotFound("Method Not Found") # pre-run not found - - except controller.UnauthorizedException: # pre-run unauthorized - raise JSONRPCUnauthorized("Unauthorized") + except controller.MethodNotFoundException, e0: + raise JSONRPCMethodNotFound(e0.message) # pre-run not found + except controller.UnauthorizedException, e1: # pre-run unauthorized + raise JSONRPCUnauthorized(e1.message) # execute method try: @@ -75,14 +74,14 @@ def handleRequest(self, data, env, shortpath): result = ctrl.execute(resolved_method, **params) else: # args result = ctrl.execute(resolved_method, *params) - except TypeError, e: - raise JSONRPCInvalidParams("Invalid Params: " + e.args[0]) - except controller.InvalidRequest, e: - raise JSONRPCInvalidRequest("Invalid Request: " + e.args[0]) + except TypeError, e0: + raise JSONRPCInvalidParams("Invalid Params: " + e0.args[0]) + except controller.InvalidRequest, e1: + raise JSONRPCInvalidRequest("Invalid Request: " + e1.args[0]) except controller.MethodNotFoundException, e2: # runtime not found raise JSONRPCMethodNotFound(e2.message) - except controller.UnauthorizedException: # runtime unauthorized - raise JSONRPCUnauthorized("Unauthorized") + except controller.UnauthorizedException, e3: # runtime unauthorized + raise JSONRPCUnauthorized(e3.message) except Exception, e: print "== Exception caught: ", str(type(e)), e, " ==" diff --git a/goldmine/protocols/rest/__init__.py b/goldmine/protocols/rest/__init__.py index 2b541a3..12b617a 100644 --- a/goldmine/protocols/rest/__init__.py +++ b/goldmine/protocols/rest/__init__.py @@ -53,26 +53,26 @@ def handleRequest(self, data, env, shortpath): # resolve method try: resolved_method = ctrl.get_method(method) - except controller.MethodNotFoundException: - raise RESTMethodNotFound("Method Not Found") # pre-run not found + except controller.MethodNotFoundException, e0: + raise RESTMethodNotFound(e0.message) # pre-run not found - except controller.UnauthorizedException: # pre-run unauthorized - raise RESTUnauthorized("Unauthorized") + except controller.UnauthorizedException, e1: # pre-run unauthorized + raise RESTUnauthorized(e1.message) # execute method try: - if isinstance(params, dict): # kwargs + if isinstance(params, dict): # kwargs result = ctrl.execute(resolved_method, **params) - else: # args + else: # args result = ctrl.execute(resolved_method, *params) - except TypeError, e: - raise RESTInvalidParams("Invalid Params: " + e.args[0]) - except controller.InvalidRequest, e: - raise RESTInvalidRequest("Invalid Request: " + e.args[0]) + except TypeError, e0: + raise RESTInvalidParams("Invalid Params: " + e0.args[0]) + except controller.InvalidRequest, e1: + raise RESTInvalidRequest("Invalid Request: " + e1.args[0]) except controller.MethodNotFoundException, e2: # runtime not found raise RESTMethodNotFound(e2.message) - except controller.UnauthorizedException: # runtime unauthorized - raise RESTUnauthorized("Unauthorized") + except controller.UnauthorizedException, e3: # runtime unauthorized + raise RESTUnauthorized(e3.message) except Exception, e: print "== Exception caught: ", str(type(e)), e, " ==" diff --git a/schema.sql b/schema.sql index a055cd5..b32a017 100644 --- a/schema.sql +++ b/schema.sql @@ -52,7 +52,6 @@ create table lineage ( unique (from_dataset_id, to_dataset_id) ); - -- DATASET drop table if exists dataset; create table dataset ( @@ -158,7 +157,7 @@ create table dataset_sequence_point ( index_id uuid not null, "value" float not null, uncertainty_value float, - uncertainty_type int + uncertainty_type int --- die soon ); create index dataset_sequence_point_parameter on dataset_sequence_point(parameter_id); @@ -204,24 +203,43 @@ create table "token" ( drop table if exists "group"; create table "group" ( - id uuid primary key, - parent_id uuid, - name varchar(50) + "id" uuid primary key, + "parent_id" uuid, + "name" varchar(50) ); -drop table if exists group_member; -create table group_member ( - user_id integer not null, - group_id integer not null, - primary key(user_id, group_id) +drop table if exists "group_member"; +create table "group_member" ( + "user_id" uuid not null, + "group_id" uuid not null, + primary key("user_id", "group_id") ); drop table if exists permission; create table permission ( - id uuid primary key, - study_id uuid, - identifier varchar(255), - group_id uuid + user_id uuid, + granted_by_id uuid not null, + granted timestamp not null, + identifier varchar(255), + primary key("user_id", "identifier") +); + +drop table if exists study_group; +create table study_group ( + "study_id" uuid not null, + "group_id" uuid not null, + "role" int not null, + primary key("study_id", "group_id") +); + +drop table if exists favorite; +create table favorite ( + "id" uuid primary key, + "name" varchar(255) not null, + "user_id" uuid not null, + "ref_id" uuid not null, + "ref_type" int not null, + unique("name", "user_id") ); insert into "user" (id, username, fullname, email, password, userlevel) values ('12345678-90ab-cdef-1234-567890abcdef', 'admin', 'Database Administrator', 'user@example.com', '$2a$12$3xJErTM6NcJSNKSFP5Chxe9O3XnVmA6V8xXpTD2Jr8Srrst.np4AS', 10);