From af313ca07bc607163336e1df98990fbbd12f2941 Mon Sep 17 00:00:00 2001 From: Gunnar Schaefer <gsfr@stanford.edu> Date: Mon, 10 Aug 2015 17:23:57 -0700 Subject: [PATCH] first pass at proper group schema delivered at: - GET /schema/group => POST schema - GET /schema/group?method=put => PUT schema --- api.py | 17 ++++++++++++++- groups.json | 51 +++++++++++++++++++++++++++++++++++++++++++++ users.py | 60 ++++++++++++++++------------------------------------- 3 files changed, 85 insertions(+), 43 deletions(-) create mode 100644 groups.json diff --git a/api.py b/api.py index f931cc8d..191b1b12 100644 --- a/api.py +++ b/api.py @@ -1,4 +1,5 @@ import os +import copy import json import webapp2 import bson.json_util @@ -23,6 +24,9 @@ routes = [ webapp2.Route(r'/sites', core.Core, handler_method='sites', methods=['GET']), webapp2.Route(r'/search', core.Core, handler_method='search', methods=['GET', 'POST']), ]), + webapp2_extras.routes.PathPrefixRoute(r'/api/schema', [ + webapp2.Route(r'/group', users.Group, handler_method='schema', methods=['GET']), + ]), webapp2.Route(r'/api/users', users.Users), webapp2_extras.routes.PathPrefixRoute(r'/api/users', [ webapp2.Route(r'/count', users.Users, handler_method='count', methods=['GET']), @@ -36,7 +40,6 @@ routes = [ webapp2.Route(r'/api/groups', users.Groups), webapp2_extras.routes.PathPrefixRoute(r'/api/groups', [ webapp2.Route(r'/count', users.Groups, handler_method='count', methods=['GET']), - webapp2.Route(r'/schema', users.Group, handler_method='schema', methods=['GET']), webapp2.Route(r'/<:[^/]+>', users.Group, name='group'), webapp2.Route(r'/<gid:[^/]+>/projects', projects.Projects, name='g_projects'), webapp2.Route(r'/<gid:[^/]+>/sessions', sessions.Sessions, name='g_sessions', methods=['GET']), @@ -93,11 +96,23 @@ routes = [ ]), ] + +for cls, schema_file in [(cls, os.path.join(os.path.dirname(__file__), schema_file)) for cls, schema_file in [ + (users.Group, 'groups.json'), + ]]: + with open(schema_file) as fp: + cls.post_schema = json.load(fp) + cls.put_schema = copy.deepcopy(cls.post_schema) + cls.put_schema['properties'].pop('_id') + cls.put_schema.pop('required') + + def dispatcher(router, request, response): rv = router.default_dispatcher(request, response) if rv is not None: response.write(json.dumps(rv, default=bson.json_util.default)) response.headers['Content-Type'] = 'application/json; charset=utf-8' + app = webapp2.WSGIApplication(routes) app.router.set_dispatcher(dispatcher) diff --git a/groups.json b/groups.json new file mode 100644 index 00000000..757c2ec9 --- /dev/null +++ b/groups.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "properties": { + "_id": { + "minLength": 2, + "maxLength": 32, + "pattern": "^[0-9a-z_-]*$", + "title": "ID", + "type": "string" + }, + "name": { + "minLength": 2, + "maxLength": 32, + "pattern": "^[0-9A-Za-z _-]*$", + "title": "Name", + "type": "string" + }, + "roles": { + "default": [], + "items": { + "additionalProperties": false, + "properties": { + "_id": { + "type": "string" + }, + "access": { + "enum": [ + "ro", + "rw", + "admin" + ], + "type": "string" + } + }, + "required": [ + "access", + "_id" + ], + "type": "object" + }, + "title": "Roles", + "type": "array", + "uniqueItems": true + } + }, + "required": [ + "_id" + ], + "title": "Group", + "type": "object" +} diff --git a/users.py b/users.py index 4829d4ee..4070a2d9 100644 --- a/users.py +++ b/users.py @@ -6,6 +6,7 @@ log = logging.getLogger('scitran.api') import copy import hashlib import pymongo +import datetime import jsonschema import base @@ -194,7 +195,10 @@ class Groups(base.RequestHandler): self.abort(403, 'must be logged in and superuser to create new group') try: json_body = self.request.json_body - jsonschema.validate(json_body, Group.json_schema) + jsonschema.validate(json_body, Group.post_schema) + json_body['created'] = datetime.datetime.utcnow() + json_body['modified'] = datetime.datetime.utcnow() + json_body.setdefault('roles', []) self.dbc.insert(json_body) except (ValueError, jsonschema.ValidationError) as e: self.abort(400, str(e)) @@ -218,6 +222,9 @@ class Groups(base.RequestHandler): query = {'roles._id': self.uid} projection += ['roles.$'] groups = list(self.app.db.groups.find(query, projection)) + #for group in groups: + # group['created'], _ = util.format_timestamp(group['created']) # TODO json serializer should do this + # group['modified'], _ = util.format_timestamp(group['modified']) # TODO json serializer should do this if self.debug: for group in groups: group['debug'] = {} @@ -231,48 +238,16 @@ class Group(base.RequestHandler): """/groups/<_id>""" - json_schema = { - '$schema': 'http://json-schema.org/draft-04/schema#', - 'title': 'Group', - 'type': 'object', - 'properties': { - '_id': { - 'title': 'Group ID', - 'type': 'string', - }, - 'name': { - 'title': 'Name', - 'type': 'string', - 'maxLength': 32, - }, - 'roles': { - 'title': 'Roles', - 'type': 'array', - 'default': [], - 'items': { - 'type': 'object', - 'properties': { - 'access': { - 'type': 'string', - 'enum': [role['rid'] for role in ROLES], - }, - '_id': { - 'type': 'string', - }, - }, - 'required': ['access', '_id'], - 'additionalProperties': False, - }, - 'uniqueItems': True, - }, - }, - 'required': ['_id'], - } - def __init__(self, request=None, response=None): super(Group, self).__init__(request, response) self.dbc = self.app.db.groups + def schema(self): + method =self.request.GET.get('method', '').lower() + if method == 'put': + return self.put_schema + return self.post_schema + def get(self, _id): """Return Group details.""" group = self.app.db.groups.find_one({'_id': _id}) @@ -282,6 +257,9 @@ class Group(base.RequestHandler): group = self.app.db.groups.find_one({'_id': _id, 'roles': {'$elemMatch': {'_id': self.uid, 'access': 'admin'}}}) if not group: self.abort(403, 'User ' + self.uid + ' is not an admin of Group ' + _id) + if 'created' in group and 'modified' in group: + group['created'], _ = util.format_timestamp(group['created']) # TODO json serializer should do this + group['modified'], _ = util.format_timestamp(group['modified']) # TODO json serializer should do this if self.debug: group['debug'] = {} group['debug']['projects'] = self.uri_for('g_projects', gid=group['_id'], _full=True) + '?' + self.request.query_string @@ -296,11 +274,9 @@ class Group(base.RequestHandler): user_perm = util.user_perm(group.get('roles', []), self.uid) if not self.superuser_request and not user_perm.get('access') == 'admin': self.abort(403, 'must be superuser or group admin to update group') - schema = copy.deepcopy(self.json_schema) - del schema['required'] try: json_body = self.request.json_body - jsonschema.validate(json_body, schema) + jsonschema.validate(json_body, self.put_schema) except (ValueError, jsonschema.ValidationError) as e: self.abort(400, str(e)) self.dbc.update({'_id': _id}, {'$set': util.mongo_dict(json_body)}) -- GitLab