diff --git a/acquisitions.py b/acquisitions.py index 951848c8e747b13997c8ddb7e0e6fae3a77703b1..0946291eeb58ba9a8e7aeb1eb062600c31a15315 100644 --- a/acquisitions.py +++ b/acquisitions.py @@ -36,7 +36,7 @@ class Acquisitions(base.RequestHandler): projection = {'label': 1, 'description': 1, 'types': 1, 'notes': 1} if self.public_request: query['public'] = True - elif not self.superuser: + elif not self.superuser_request: query['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}} projection['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}} acquisitions = list(self.dbc.find(query, projection)) diff --git a/base.py b/base.py index d6a883847d0ca630b3ebb40e0b87448e944698d7..9b1137ea4a04de11af9cf1a5972ab3f39f3e1ea0 100644 --- a/base.py +++ b/base.py @@ -38,6 +38,13 @@ ROLES = [ INTEGER_ROLES = {r['rid']: r['sort'] for r in ROLES} +def mongo_dict(d): + def _mongo_list(d, pk=''): + pk = pk and pk + '.' + return sum([_mongo_list(v, pk+k) if isinstance(v, dict) else [(pk+k, v)] for k, v in d.iteritems()], []) + return dict(_mongo_list(d)) + + class RequestHandler(webapp2.RequestHandler): """fetches pubkey from own self.db.remotes. needs to be aware of OWN site uid""" @@ -112,11 +119,13 @@ class RequestHandler(webapp2.RequestHandler): if not self.app.db.remotes.find_one({'_id': remote_instance}): self.abort(402, remote_instance + ' is not authorized') self.public_request = not bool(self.uid) - if not self.public_request: - user = self.app.db.users.find_one({'_id': self.uid}, ['superuser']) + if self.public_request or self.source_site: + self.superuser_request = False + else: + user = self.app.db.users.find_one({'_id': self.uid}, ['root', 'wheel']) if not user: self.abort(403, 'user ' + self.uid + ' does not exist') - self.superuser = not self.public_request and not self.source_site and user.get('superuser') + self.superuser_request = user.get('root') and user.get('wheel') def dispatch(self): """dispatching and request forwarding""" @@ -161,9 +170,12 @@ class RequestHandler(webapp2.RequestHandler): headers['Access-Control-Allow-Origin'] = self.response.headers['Access-Control-Allow-Origin'] webapp2.abort(code, *args, **kwargs) + def handle_exception(self, exception, debug): + self.abort(500, exception.message) + def options(self, *args, **kwargs): self.response.headers['Access-Control-Allow-Methods'] = 'GET, HEAD, POST, PUT, DELETE, OPTIONS' - self.response.headers['Access-Control-Allow-Headers'] = 'Authorization' + self.response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type' self.response.headers['Access-Control-Max-Age'] = '151200' def schema(self, updates={}): @@ -182,7 +194,7 @@ class Container(RequestHandler): if not container.get('public', False): self.abort(403, 'this ' + self.__class__.__name__ + 'is not public') del container['permissions'] - elif not self.superuser: + elif not self.superuser_request: user_perm = None for perm in container['permissions']: if perm['uid'] == self.uid and perm.get('site') == self.source_site: @@ -200,7 +212,7 @@ class Container(RequestHandler): class AcquisitionAccessChecker(object): def check_acq_list(self, acq_ids): - if not self.superuser: + if not self.superuser_request: for a_id in acq_ids: agg_res = self.app.db.acquisitions.aggregate([ {'$match': {'_id': a_id}}, diff --git a/bootstrap.json.sample b/bootstrap.json.sample index 2afa1d3d3b16fadb5369225ce4a4d289462d8726..ac13d52541be0e3f088d39b710695a7fc927e603 100644 --- a/bootstrap.json.sample +++ b/bootstrap.json.sample @@ -17,7 +17,7 @@ "email": "user1@example.com", "firstname": "First", "lastname": "User", - "superuser": true + "wheel": true } ] } diff --git a/projects.py b/projects.py index 637edca7222805de605dab61282fcef5b0607b4b..a1cdb91583bdebd64fbdfe51994c72d85ddc0a97 100644 --- a/projects.py +++ b/projects.py @@ -31,7 +31,7 @@ class Projects(base.RequestHandler): query = None if self.public_request: query = {'public': True} - elif not self.superuser: + elif not self.superuser_request: projection['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}} if self.request.get('admin').lower() in ('1', 'true'): query = {'permissions': {'$elemMatch': {'uid': self.uid, 'site': self.source_site, 'access': 'admin'}}} diff --git a/sessions.py b/sessions.py index 51d15eb764622ce33b6b7133fc9670920f183f35..e78efef8540fad7354fe0d93d4b4c1574159844e 100644 --- a/sessions.py +++ b/sessions.py @@ -36,7 +36,7 @@ class Sessions(base.RequestHandler): projection = {'label': 1, 'subject.code': 1, 'notes': 1} if self.public_request: query['public'] = True - elif not self.superuser: + elif not self.superuser_request: query['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}} projection['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}} sessions = list(self.dbc.find(query, projection)) diff --git a/users.py b/users.py index 47ea5bd6cb6140109030504d3a303681371de156..58f1efdd351b95061d37cac24839e186902c80bf 100644 --- a/users.py +++ b/users.py @@ -28,14 +28,12 @@ class Users(base.RequestHandler): """Create a new User""" if self.public_request: # FIXME: who is allowed to create a new user? self.abort(403, 'must be logged in to create new user') - schema = copy.deepcopy(User.json_schema) - schema['required'] += ['email', 'firstname', 'lastname'] - json_body = self.request.json_body try: - jsonschema.validate(json_body, schema) + json_body = self.request.json_body + jsonschema.validate(json_body, User.json_schema) json_body['email_hash'] = hashlib.md5(json_body['email']).hexdigest() self.dbc.insert(json_body) - except jsonschema.ValidationError as e: + except (ValueError, jsonschema.ValidationError) as e: self.abort(400, str(e)) except pymongo.errors.DuplicateKeyError as e: self.abort(400, 'User ID %s already exists' % json_body['_id']) @@ -44,7 +42,7 @@ class Users(base.RequestHandler): """Return the list of Users.""" if self.public_request: self.abort(403, 'must be logged in to retrieve User list') - users = list(self.dbc.find({}, ['firstname', 'lastname', 'email_hash', 'superuser'])) + users = list(self.dbc.find({}, ['firstname', 'lastname', 'email_hash', 'wheel'])) if self.debug: for user in users: user['details'] = self.uri_for('user', _id=str(user['_id']), _full=True) + '?' + self.request.query_string @@ -80,8 +78,12 @@ class User(base.RequestHandler): 'email_hash': { 'type': 'string', }, - 'superuser': { - 'title': 'Superuser', + 'root': { + 'title': 'Root', + 'type': 'boolean', + }, + 'wheel': { + 'title': 'Wheel', 'type': 'boolean', }, 'preferences': { @@ -95,7 +97,7 @@ class User(base.RequestHandler): }, }, }, - 'required': ['_id'], + 'required': ['_id', 'email', 'firstname', 'lastname'], 'additionalProperties': False, } @@ -105,7 +107,9 @@ class User(base.RequestHandler): def self(self): """Return details for the current User.""" - return self.dbc.find_one({'_id': self.uid}, ['firstname', 'lastname', 'superuser', 'preferences']) + user = self.dbc.find_one({'_id': self.uid}, ['firstname', 'lastname', 'root', 'wheel', 'preferences', 'email_hash']) + user.setdefault('preferences', {}) + return user def get(self, _id): """ Return User details.""" @@ -119,7 +123,7 @@ class User(base.RequestHandler): user = self.dbc.find_one({'_id': _id}, projection or None) if not user: self.abort(404, 'no such User') - if self.debug and (self.superuser or _id == self.uid): + if self.debug and (self.superuser_request or _id == self.uid): user['groups'] = self.uri_for('groups', _id=_id, _full=True) + '?' + self.request.query_string return user @@ -127,26 +131,23 @@ class User(base.RequestHandler): """Update an existing User.""" user = self.dbc.find_one({'_id': _id}) if not user: - self.abort(404) - if _id == self.uid or self.superuser: # users can only update their own info - updates = {'$set': {'_id': _id}, '$unset': {'__null__': ''}} - for k, v in self.request.params.iteritems(): - if k != 'superuser' and k in []:#user_fields: - updates['$set'][k] = v # FIXME: do appropriate type conversion - elif k == 'superuser' and _id == self.uid and self.superuser is not None: # toggle superuser for requesting user - updates['$set'][k] = v.lower() in ('1', 'true') - elif k == 'superuser' and _id != self.uid and self.superuser: # enable/disable superuser for other user - if v.lower() in ('1', 'true') and user.get('superuser') is None: - updates['$set'][k] = False # superuser is tri-state: False indicates granted, but disabled, superuser privileges - elif v.lower() not in ('1', 'true'): - updates['$unset'][k] = '' - self.dbc.update({'_id': _id}, updates) - else: - self.abort(403) + self.abort(404, 'no such User') + if not self.superuser_request and _id != self.uid: + self.abort(403, 'must be superuser to update another User') + schema = copy.deepcopy(self.json_schema) + del schema['required'] + try: + json_body = self.request.json_body + jsonschema.validate(json_body, schema) + except (ValueError, jsonschema.ValidationError) as e: + self.abort(400, str(e)) + if 'wheel' in json_body and _id == self.uid: + self.abort(400, 'user cannot alter own superuser privilege') + self.dbc.update({'_id': _id}, {'$set': base.mongo_dict(json_body)}) def delete(self, _id): """Delete a User.""" - if not self.superuser: + if not self.superuser_request: self.abort(403, 'must be superuser to delete a User') self.dbc.remove({'_id': _id}) @@ -167,11 +168,11 @@ class Groups(base.RequestHandler): """Return the list of Groups.""" query = None if _id is not None: - if _id != self.uid and not self.superuser: + if _id != self.uid and not self.superuser_request: self.abort(403, 'User ' + self.uid + ' may not see the Groups of User ' + _id) query = {'roles.uid': _id} else: - if not self.superuser: + if not self.superuser_request: if self.request.get('admin').lower() in ('1', 'true'): query = {'roles': {'$elemMatch': {'uid': self.uid, 'access': 'admin'}}} else: @@ -228,7 +229,7 @@ class Group(base.RequestHandler): group = self.app.db.groups.find_one({'_id': _id}) if not group: self.abort(404, 'no such Group: ' + _id) - if not self.superuser: + if not self.superuser_request: group = self.app.db.groups.find_one({'_id': _id, 'roles': {'$elemMatch': {'uid': self.uid, 'access': 'admin'}}}) if not group: self.abort(403, 'User ' + self.uid + ' is not an admin of Group ' + _id)