def get_permissions(self, user, loaded): hoisted_read = Permissions.get_permissions( user, loaded[-1]) & Permissions.READ if self.resource: if super().get_permissions(user, loaded) != Permissions.ADMIN: loaded = loaded[:-1] return super().get_permissions(user, loaded) | hoisted_read
def _validate_patch(self, patch): for blob in patch: if not blob['path'].split('/')[1] in Namespace.WHITELIST: raise exceptions.InvalidField(blob['path']) if blob.get('value') and blob['path'].startswith('/permissions'): blob['value'] = Permissions.from_string(blob['value']) return patch
def create_collection(self, name, user, logger=None, storage=None, state=None, permissions=None, schema=None, plugins=None, **kwargs): if kwargs: raise exceptions.InvalidFields(kwargs.keys()) uid = str(uuid.uuid4()).replace('-', '') state = state or settings.COLLECTION_BACKENDS['state'] logger = logger or settings.COLLECTION_BACKENDS['logger'] storage = storage or settings.COLLECTION_BACKENDS['storage'] if isinstance(permissions or {}, dict): try: permissions = { key: Permissions.from_string(value) for key, value in (permissions or {}).items() } permissions[user] = Permissions.ADMIN except KeyError as e: raise exceptions.InvalidPermission(e.args[0]) except AttributeError: pass # Schema validation will catch issues collection_dict = { 'uuid': uid, 'plugins': plugins or {}, 'schema': schema, 'permissions': permissions, 'logger': { 'backend': logger, 'settings': get_backend(logger).settings_for(self.uuid, uid, 'logger') }, 'state': { 'backend': state, 'settings': get_backend(state).settings_for(self.uuid, uid, 'state') }, 'storage': { 'backend': storage, 'settings': get_backend(storage).settings_for(self.uuid, uid, 'storage') } } # Validate that our inputs can actually be deserialized to a collection Collection(Document( ref=name, log_ref=None, data_ref=None, created_on=0, created_by='', modified_on=0, modified_by='', data=collection_dict, )) try: return Collection(self.create(name, collection_dict, user)) except exceptions.KeyExists: raise exceptions.KeyExists( code='C409', title='Collection already exists', detail='Collection "{}" already exists in namespace "{}"'.format(name, self.ref) )
def prepare(self): super().prepare() if self.request.method == 'OPTIONS': return # Dont do anything for OPTIONS requests resources = [] resource = self.resource while resource: resources = [resource] + resources resource = resource.parent loaded = [ r.load(self.path_kwargs[r.name + '_id'], self.request) for r in resources if self.path_kwargs.get(r.name + '_id') ] self.permissions = Permissions.get_permissions(self.current_user, *loaded) # TODO this is kinda hacky self.current_user.permissions = self.permissions # TODO 404s get raised before permissions are checked required_permissions = self.resource.get_permissions(self.request) if (required_permissions & self.permissions) != required_permissions: if self.current_user.uid is None: raise exceptions.Unauthorized() raise exceptions.Forbidden(required_permissions)
def attributes(self): return { 'name': self._instance.ref, 'permissions': { sel: Permissions(perm).name for sel, perm in self._instance.data['permissions'].items() } }
def attributes(cls, inst): return { 'name': inst.ref, 'permissions': { sel: Permissions(perm).name for sel, perm in inst.data['permissions'].items() } }
def attributes(cls, inst): return { 'name': inst.ref, 'schema': inst.data.get('schema'), # 'documentCreatorPermissions': Permissions(inst.data['documentCreatorPermissions']).name, 'permissions': { sel: Permissions(perm).name for sel, perm in inst.data['permissions'].items() } }
def create_namespace(self, name, user, permissions=None, **kwargs): if kwargs: raise exceptions.InvalidFields(kwargs.keys()) uid = str(uuid.uuid4()).replace('-', '') if isinstance(permissions or {}, dict): try: permissions = { key: Permissions.from_string(value) for key, value in (permissions or {}).items() } permissions[user] = Permissions.ADMIN except KeyError as e: raise exceptions.InvalidPermission(e.args[0]) except AttributeError: pass # Schema validation will catch issues try: self.create( name, { 'uuid': uid, 'permissions': { **(permissions or {}), user: Permissions.ADMIN }, 'logger': { 'backend': settings.NAMESPACE_BACKENDS['logger'], 'settings': get_backend(settings.NAMESPACE_BACKENDS['logger']). settings_for(self.uuid, uid, 'logger') }, 'state': { 'backend': settings.NAMESPACE_BACKENDS['state'], 'settings': get_backend( settings.NAMESPACE_BACKENDS['state']).settings_for( self.uuid, uid, 'state') }, 'storage': { 'backend': settings.NAMESPACE_BACKENDS['storage'], 'settings': get_backend(settings.NAMESPACE_BACKENDS['storage']). settings_for(self.uuid, uid, 'storage') } }, user) except exceptions.KeyExists: raise exceptions.KeyExists( code='N409', title='Namespace already exists', detail='Namespace "{}" already exists'.format(name)) return self.get_namespace(name)
def attributes(self): full = self._permission == Permissions.ADMIN or ( (self._permission ^ Permissions.get_permissions( self._user, Collection(self._instance))) & Permissions.READ) == Permissions.READ return { 'name': self._instance.ref, 'schema': self._instance.data.get('schema'), **({} if not full else { 'plugins': self._instance.data.get('plugins', {}), 'permissions': { sel: Permissions(perm).name for sel, perm in self._instance.data['permissions'].items( ) } }) }
async def patch(self, handler): if not handler.payload: raise exceptions.BadRequest() if not handler.payload.attributes.get('id'): raise exceptions.BadRequest(detail='Id must be provided') error = None id = handler.payload.attributes.pop('id') try: doc = self.collection.read(id) except exceptions.NotFound as e: error = e permissions = handler.current_user.permissions | Permissions.get_permissions( handler.current_user, doc) if error or not (permissions & Permissions.UPDATE): return handler.write( {'data': { 'id': id, 'type': 'suppressions', 'attributes': {} }}) email = self.extract_email(doc=doc) headers = {'Authorization': 'Bearer {}'.format(self.sendgrid_key)} for group, subscribe in list(handler.payload.attributes.items()): if subscribe: async with aiohttp.post( 'https://api.sendgrid.com/v3/asm/groups/{}/suppressions' .format(group), headers=headers, data=json.dumps({'recipient_emails': [email]})) as response: assert response.status == 201 # TODO Handle errors else: async with aiohttp.delete( 'https://api.sendgrid.com/v3/asm/groups/{}/suppressions/{}' .format(group, quote(email)), headers=headers) as response: assert response.status == 204 # TODO Handle errors handler.payload.attributes[group] = bool(subscribe) return handler.write({ 'data': { 'id': id, 'type': 'supressions', 'attributes': handler.payload.attributes } })
def attributes(self): full = self._permission == Permissions.ADMIN or ((self._permission ^ Permissions.get_permissions(self._user, Collection(self._instance))) & Permissions.READ) == Permissions.READ return { 'name': self._instance.ref, 'schema': self._instance.data.get('schema'), **({} if not full else { 'plugins': self._instance.data.get('plugins', {}), 'permissions': { sel: Permissions(perm).name for sel, perm in self._instance.data['permissions'].items() } }) }
def meta(self): return { 'permissions': Permissions(self._permission).name, 'created-by': self._instance.created_by, 'modified-by': self._instance.modified_by, 'created-on': datetime.datetime.fromtimestamp( self._instance.created_on).isoformat(), 'modified-on': datetime.datetime.fromtimestamp( self._instance.modified_on).isoformat() }
def prepare(self): super().prepare() if self.request.method == 'OPTIONS': return # Dont do anything for OPTIONS requests loaded = [] try: for view in self._view_class.lineage(): key = view.name + '_id' if self.path_kwargs[key] is None: break loaded.append(view.load(self.path_kwargs[key], *loaded)) except exceptions.NotFound as e: err = e # Load as many resources as are available to do a permissions check # A 404 will be thrown if the user has the required permissions self._view = view(*loaded) else: err = None self._view = self._view_class(*loaded) # If this is a relationship swap out the current view with the relation if 'relationship' in self.path_kwargs: relationship = self._serializer.relations[self.path_kwargs['relationship']] self._view = relationship.view(*loaded) self._serializer = relationship.serializer() permissions = Permissions.get_permissions(self.current_user, *loaded) required_permissions = self._view.get_permissions(self.request) # For use later on self.current_user.permissions = permissions # Check permissions if (required_permissions & permissions) != required_permissions: if self.current_user.uid is None: raise exceptions.Unauthorized() raise exceptions.Forbidden(required_permissions) # Not found is always raised AFTER permissions checks if err: raise err if self.request.method in ('GET', 'DELETE'): return # GET and DELETE bodies are ignored self.payload # Force payload to load and validate
async def patch(self, handler): if not handler.payload: raise exceptions.BadRequest() if not handler.payload.attributes.get('id'): raise exceptions.BadRequest(detail='Id must be provided') error = None id = handler.payload.attributes.pop('id') try: doc = self.collection.read(id) except exceptions.NotFound as e: error = e permissions = handler.current_user.permissions | Permissions.get_permissions(handler.current_user, doc) if error or not (permissions & Permissions.UPDATE): return handler.write({ 'data': { 'id': id, 'type': 'suppressions', 'attributes': {} } }) email = self.extract_email(doc=doc) headers = {'Authorization': 'Bearer {}'.format(self.sendgrid_key)} for group, subscribe in list(handler.payload.attributes.items()): if subscribe: async with aiohttp.post('https://api.sendgrid.com/v3/asm/groups/{}/suppressions'.format(group), headers=headers, data=json.dumps({'recipient_emails': [email]})) as response: assert response.status == 201 # TODO Handle errors else: async with aiohttp.delete('https://api.sendgrid.com/v3/asm/groups/{}/suppressions/{}'.format(group, quote(email)), headers=headers) as response: assert response.status == 204 # TODO Handle errors handler.payload.attributes[group] = bool(subscribe) return handler.write({ 'data': { 'id': id, 'type': 'supressions', 'attributes': handler.payload.attributes } })
async def get(self, handler): error = None id = handler.get_query_argument('id') try: doc = self.collection.read(id) except exceptions.NotFound as e: error = e permissions = handler.current_user.permissions | Permissions.get_permissions( handler.current_user, doc) if error or not (permissions & Permissions.READ): return handler.write( {'data': { 'id': id, 'type': 'suppressions', 'attributes': {} }}) attrs = {} email = self.extract_email(doc=doc) groups = handler.get_query_arguments('group[]') for group in groups: async with aiohttp.get( 'https://api.sendgrid.com/v3/asm/groups/{}/suppressions'. format(group), headers={ 'Authorization': 'Bearer {}'.format(self.sendgrid_key) }) as response: if response.status != 200 or not isinstance( await response.json(), list): attrs[group] = False else: attrs[group] = email in await response.json() return handler.write( {'data': { 'id': id, 'type': 'supressions', 'attributes': attrs }})
def create_namespace(self, name, user, permissions=None, **kwargs): if kwargs: raise exceptions.InvalidFields(kwargs.keys()) uid = str(uuid.uuid4()).replace('-', '') if isinstance(permissions or {}, dict): try: permissions = { key: Permissions.from_string(value) for key, value in (permissions or {}).items() } permissions[user] = Permissions.ADMIN except KeyError as e: raise exceptions.InvalidPermission(e.args[0]) except AttributeError: pass # Schema validation will catch issues try: self.create(name, { 'uuid': uid, 'permissions': {**(permissions or {}), user: Permissions.ADMIN}, 'logger': { 'backend': settings.NAMESPACE_BACKENDS['logger'], 'settings': get_backend(settings.NAMESPACE_BACKENDS['logger']).settings_for(self.uuid, uid, 'logger') }, 'state': { 'backend': settings.NAMESPACE_BACKENDS['state'], 'settings': get_backend(settings.NAMESPACE_BACKENDS['state']).settings_for(self.uuid, uid, 'state') }, 'storage': { 'backend': settings.NAMESPACE_BACKENDS['storage'], 'settings': get_backend(settings.NAMESPACE_BACKENDS['storage']).settings_for(self.uuid, uid, 'storage') } }, user) except exceptions.KeyExists: raise exceptions.KeyExists( code='N409', title='Namespace already exists', detail='Namespace "{}" already exists'.format(name) ) return self.get_namespace(name)
def update(self, key, patch, user): if isinstance(patch, dict): keys = set(patch.keys()) if not keys.issubset(Collection.WHITELIST): raise exceptions.InvalidFields(keys - Collection.WHITELIST) previous = self._state.get(key) patch = jsonpatch.JsonPatch.from_diff(previous.data, {**previous.data, **patch}) patch = list(filter(lambda p: p['path'].split('/')[1] in Namespace.WHITELIST, patch)) for blob in patch: if not blob['path'].split('/')[1] in Collection.WHITELIST: raise exceptions.InvalidField(blob['path']) if blob.get('value') and not isinstance(blob.get('value'), Permissions) and blob['path'].startswith('/permissions'): try: blob['value'] = Permissions(reduce(operator.or_, [Permissions[p.strip()] for p in blob['value'].split(',')], Permissions.NONE)) except (AttributeError, KeyError): raise exceptions.InvalidPermission(blob['value']) return super().update(key, patch, user)
async def get(self, handler): error = None id = handler.get_query_argument('id') try: doc = self.collection.read(id) except exceptions.NotFound as e: error = e permissions = handler.current_user.permissions | Permissions.get_permissions(handler.current_user, doc) if error or not (permissions & Permissions.READ): return handler.write({ 'data': { 'id': id, 'type': 'suppressions', 'attributes': {} } }) attrs = {} email = self.extract_email(doc=doc) groups = handler.get_query_arguments('group[]') for group in groups: async with aiohttp.get('https://api.sendgrid.com/v3/asm/groups/{}/suppressions'.format(group), headers={'Authorization': 'Bearer {}'.format(self.sendgrid_key)}) as response: if response.status != 200 or not isinstance(await response.json(), list): attrs[group] = False else: attrs[group] = email in await response.json() return handler.write({ 'data': { 'id': id, 'type': 'supressions', 'attributes': attrs } })
def get_permissions(self, request): return Permissions.from_method(request.method)
def __init__(self, request, user, inst, *parents): super().__init__(request, user, inst, *parents) self._permission |= Permissions.get_permissions(user, Namespace(inst))
def collection_permissions(self): return Permissions.from_string(self._raw.get('collection', ''))
def document_permissions(self): return Permissions.from_string(self._raw.get('document', ''))
def get_permissions(self, user, loaded): hoisted_read = Permissions.get_permissions(user, loaded[-1]) & Permissions.READ if self.resource: if super().get_permissions(user, loaded) != Permissions.ADMIN: loaded = loaded[:-1] return super().get_permissions(user, loaded) | hoisted_read
def get_permissions(self, user, loaded): return Permissions.get_permissions(user, *loaded)
def create_collection(self, name, user, logger=None, storage=None, state=None, permissions=None, schema=None, plugins=None, **kwargs): if kwargs: raise exceptions.InvalidFields(kwargs.keys()) uid = str(uuid.uuid4()).replace('-', '') state = state or settings.COLLECTION_BACKENDS['state'] logger = logger or settings.COLLECTION_BACKENDS['logger'] storage = storage or settings.COLLECTION_BACKENDS['storage'] if isinstance(permissions or {}, dict): try: permissions = { key: Permissions.from_string(value) for key, value in (permissions or {}).items() } permissions[user] = Permissions.ADMIN except KeyError as e: raise exceptions.InvalidPermission(e.args[0]) except AttributeError: pass # Schema validation will catch issues collection_dict = { 'uuid': uid, 'plugins': plugins or {}, 'schema': schema, 'permissions': permissions, 'logger': { 'backend': logger, 'settings': get_backend(logger).settings_for(self.uuid, uid, 'logger') }, 'state': { 'backend': state, 'settings': get_backend(state).settings_for(self.uuid, uid, 'state') }, 'storage': { 'backend': storage, 'settings': get_backend(storage).settings_for(self.uuid, uid, 'storage') } } # Validate that our inputs can actually be deserialized to a collection Collection( Document( ref=name, log_ref=None, data_ref=None, created_on=0, created_by='', modified_on=0, modified_by='', data=collection_dict, )) try: return Collection(self.create(name, collection_dict, user)) except exceptions.KeyExists: raise exceptions.KeyExists( code='C409', title='Collection already exists', detail='Collection "{}" already exists in namespace "{}"'. format(name, self.ref))