def resolveQuery(self, session, query): """Get the object IDs that match a query. @param session: The L{FluidinfoSession} for the request. @param query: The query to resolve. @raise TBadRequest: If the given query is not encoded properly. @raise TParseError: If the query is not well formed. @raise TNonexistentTag: If the user doesn't have read permissions on the tags in the query. @return: A C{Deferred} that will fire with a C{list} of object ID C{str}s that match the query. """ try: query = query.decode('utf-8') except UnicodeDecodeError as error: session.log.exception(error) error = TBadRequest('Query string %r was not valid UTF-8.' % query) return fail(error) try: parsedQuery = parseQuery(query) except QueryParseError as error: session.log.exception(error) return fail(TParseError(query, error.message)) except IllegalQueryError as error: return fail(TBadRequest(str(error))) def run(): objects = SecureObjectAPI(session.auth.user) objectIDs = self._resolveQuery(session, objects, parsedQuery) return [str(objectID) for objectID in objectIDs] return session.transact.run(run)
def run(): if not request.isSecure() and not getDevelopmentMode(): raise TBadRequest( '/users/<username>/verify requests must use HTTPS') dictionary = registry.checkRequest(usage, request) user = cachingGetUser(self.username.decode('utf-8')) if not user: raise TNoSuchUser(self.username) password = dictionary['password'] if checkPassword(password, user.passwordHash): # FIXME Hard-coding the 'anon' consumer here isn't great, # but for now it means we don't have to change the public # API. -jkakar api = OAuthConsumerAPI() consumer = cachingGetUser(u'anon') accessToken = api.getAccessToken(consumer, user) renewalToken = api.getRenewalToken(consumer, user) return {'accessToken': accessToken.encrypt(), 'fullname': user.fullname, 'renewalToken': renewalToken.encrypt(), 'role': str(user.role), 'valid': True} else: return {'valid': False}
def getRecentObjectActivity(self, session, objectID): """Get information about recent tag values on the given object. @param session: The L{FluidinfoSession} for the request. @param objectID: A C{str} representing the ID of the object. @return: A C{list} of C{dict}s matching the following format:: [{'tag': <path>, 'id': <object-id>, 'about': <about-value>, 'value': <value>, 'username': <username>, 'updated-at': <timestamp>}, ...] """ try: objectID = UUID(objectID) except ValueError: error = TBadRequest('Invalid UUID: %r.' % objectID) return fail(error) def run(): recentActivity = SecureRecentActivityAPI(session.auth.user) result = recentActivity.getForObjects([objectID]) return self._formatResult(result) return session.transact.run(run)
def deleteValuesForQuery(self, session, query, tags=None): """Delete L{TagValue}s that match a query. @param session: The L{FluidinfoSession} for the request. @param query: The query to resolve. @param tags: Optionally, the sequence of L{Tag.path}s to delete values for. @raise TNonexistentTag: Raised if any of the L{Tag}s in the L{Query} or to delete does not exist. @raise TPathPermissionDenied: Raised if the L{User} does not have L{Operation.READ_TAG_VALUE} permission on any of the L{Tag}s in the L{Query} or does not have L{Operation.DELETE_TAG_VALUE} permission on any of the L{Tag}s to set. @raise TParseError: Raised if the L{Query} can't be parsed. """ try: parsedQuery = parseQuery(query.decode('utf-8')) except QueryParseError as error: session.log.exception(error) return fail(TParseError(query, error.message)) except IllegalQueryError as error: return fail(TBadRequest(str(error))) if tags is not None: tags = [tag.decode('utf-8') for tag in tags] def run(): tagValues = SecureTagValueAPI(session.auth.user) objects = SecureObjectAPI(session.auth.user) objectIDs = self._resolveQuery(session, objects, parsedQuery) values = [] if tags is None: # delete all tags user has permissions for result = objects.getTagsByObjects(objectIDs, Operation.DELETE_TAG_VALUE) for objectID, paths in result.iteritems(): for path in paths: values.append((objectID, path)) else: # delete only tags requested by user result = objects.getTagsByObjects(objectIDs) for objectID, paths in result.iteritems(): for path in paths: if tags is None or path in tags: values.append((objectID, path)) if values: try: tagValues.delete(values) except UnknownPathError as error: session.log.exception(error) path = error.paths[0] raise TNonexistentTag(path.encode('utf-8')) except PermissionDeniedError as error: session.log.exception(error) path_, operation = error.pathsAndOperations[0] category, action = getCategoryAndAction(operation) raise TPathPermissionDenied(category, action, path_) return session.transact.run(run)
def getPermission(self, session, category, action, path): """Get permissions for a given path. @param session: The L{AuthenticatedSession} for the request. @param category: A C{unicode} indicating the category of the permission. @param action: A C{unicode} indicating the action of the permission. @param path: The L{Namespace.path} or L{Tag.path} to get permissions from. @raise TBadRequest: Raised if the given C{action} or C{category} are invalid. @raise TNonexistentNamespace: Raised if the given L{Namespace} path does not exist. @raise TNonexistentTag: Raised if the given L{Tag} path does not exist. @raise TPathPermissionDenied: Raised if the user does not have C{CONTROL} permissions on the given L{Namespace} or L{Tag}. @return: A C{Deferred} that will fire with a L{TPolicyAndExceptions} object containing the policy and exceptions list for the requested permission. """ path = path.decode('utf-8') try: operation = getOperation(category, action) except KeyError as error: session.log.exception(error) error = TBadRequest('Action %r not possible on category %r.' % (action, category)) return defer.fail(error) def run(): permissions = SecurePermissionAPI(session.auth.user) try: result = permissions.get([(path, operation)]) except UnknownPathError as error: session.log.exception(error) unknownPath = error.paths[0] if operation in Operation.TAG_OPERATIONS: raise TNonexistentTag(unknownPath.encode('utf-8')) if operation in Operation.NAMESPACE_OPERATIONS: raise TNonexistentNamespace(unknownPath.encode('utf-8')) raise except PermissionDeniedError as error: session.log.exception(error) deniedPath, deniedOperation = error.pathsAndOperations[0] deniedCategory, deniedAction = getCategoryAndAction( deniedOperation) raise TPathPermissionDenied(deniedPath, deniedCategory, deniedAction) policy, exceptions = result[(path, operation)] policy = str(policy).lower() return TPolicyAndExceptions(policy=policy, exceptions=exceptions) return session.transact.run(run)
def getRecentUserActivityForQuery(self, session, query): """ Get information about recent tag values by the users whose objects are returned by the given query. @param session: The L{FluidinfoSession} for the request. @param query: A UTF-8 C{str} with the query to resolve. @return: A C{list} of C{dict}s matching the following format:: [{'tag': <path>, 'id': <object-id>, 'about': <about-value>, 'value': <value>, 'username': <username>, 'updated-at': <timestamp>}, ...] """ try: # Extend the query to get only objects for users. query = '(%s) AND HAS fluiddb/users/username' % query parsedQuery = parseQuery(query.decode('utf-8')) except QueryParseError as error: session.log.exception(error) return fail(TParseError(query, error.message)) except IllegalQueryError as error: return fail(TBadRequest(str(error))) def run(): objects = SecureObjectAPI(session.auth.user) # _resolveQuery is implemented in FacadeTagValueMixin objectIDs = self._resolveQuery(session, objects, parsedQuery) if not objectIDs: return [] # FIXME: This sucks, but right now if the query returns too many # objects, RecentActivityAPI will blow up. While we fix this, it's # better to return a 400 than a 500. if len(objectIDs) > 2000: raise TBadRequest('The given query returns to many objects.') values = SecureTagValueAPI(session.auth.user) result = values.get(objectIDs, [u'fluiddb/users/username']) usernames = [result[objectID][u'fluiddb/users/username'].value for objectID in result] recentActivity = SecureRecentActivityAPI(session.auth.user) result = recentActivity.getForUsers(usernames) return self._formatResult(result) return session.transact.run(run)
def run(): objects = SecureObjectAPI(session.auth.user) # _resolveQuery is implemented in FacadeTagValueMixin objectIDs = self._resolveQuery(session, objects, parsedQuery) # FIXME: This sucks, but right now if the query returns too many # objects, RecentActivityAPI will blow up. While we fix this, it's # better to return a 400 than a 500. if len(objectIDs) > 2000: raise TBadRequest('The given query returns to many objects.') recentActivity = SecureRecentActivityAPI(session.auth.user) result = recentActivity.getForObjects(objectIDs) return self._formatResult(result)
def run(): try: SecureUserAPI(session.auth.user).delete([username]) except UnknownUserError as error: session.log.exception(error) raise TNoSuchUser(username) except PermissionDeniedError as error: session.log.exception(error) deniedPath, operation = error.pathsAndOperations[0] deniedPath = deniedPath.encode('utf-8') category, action = getCategoryAndAction(operation) raise TPathPermissionDenied(category, action, deniedPath) except NotEmptyError as error: session.log.exception(error) raise TBadRequest("Can't delete user %r because they have " 'data.' % username)
def updateUser(self, session, info): """Update information about a L{User}. @param session: The L{AuthenticatedSession} for the request. @param info: A L{TUserUpdate} with information about a L{User}. @raise TNoSuchUser: Raised if the specified L{User} doesn't exist. @raise TPathPermissionDenied: Raised if the L{User} requesting the update is not a superuser. @return: A C{Deferred} that will fire after the update has been performed. """ info.username = info.username.decode('utf-8').lower() if info.password is not None: info.password = info.password.decode('utf-8') if info.name is not None: info.name = info.name.decode('utf-8') if info.email is not None: info.email = info.email.decode('utf-8') if info.role is not None: try: info.role = Role.fromName(info.role) except LookupError: return fail(TBadRequest('Invalid role given.')) def run(): try: [(objectID, _)] = SecureUserAPI(session.auth.user).set([ (info.username, info.password, info.name, info.email, info.role) ]) except UnknownUserError as error: session.log.exception(error) raise TNoSuchUser(info.username.encode('utf-8')) except PermissionDeniedError as error: session.log.exception(error) deniedPath, operation = error.pathsAndOperations[0] deniedPath = deniedPath.encode('utf-8') category, action = getCategoryAndAction(operation) raise TPathPermissionDenied(category, action, deniedPath) return str(objectID) return session.transact.run(run)
def run(): objects = SecureObjectAPI(session.auth.user) # _resolveQuery is implemented in FacadeTagValueMixin objectIDs = self._resolveQuery(session, objects, parsedQuery) if not objectIDs: return [] # FIXME: This sucks, but right now if the query returns too many # objects, RecentActivityAPI will blow up. While we fix this, it's # better to return a 400 than a 500. if len(objectIDs) > 2000: raise TBadRequest('The given query returns to many objects.') values = SecureTagValueAPI(session.auth.user) result = values.get(objectIDs, [u'fluiddb/users/username']) usernames = [result[objectID][u'fluiddb/users/username'].value for objectID in result] recentActivity = SecureRecentActivityAPI(session.auth.user) result = recentActivity.getForUsers(usernames) return self._formatResult(result)
def updatePermission(self, session, category, action, path, policyAndExceptions): """Update permissions for a given path. @param session: The L{AuthenticatedSession} for the request. @param category: A C{unicode} indicating the category of the permission. @param action: A C{unicode} indicating the action of the permission. @param path: The L{Namespace.path} or L{Tag.path} to get permissions from. @param policyAndExceptions: A L{TPolicyAndExceptions} object containing the policy and exceptions list for the permission. @raise TBadRequest: Raised if the given C{action} or C{category} are invalid. @raise TInvalidPolicy: Raised if the policy given in C{policyAndExceptions} is invalid. @raise TNonexistentNamespace: Raised if the given L{Namespace} path does not exist. @raise TNonexistentTag: Raised if the given L{Tag} path does not exist. @raise TPathPermissionDenied: Raised if the user does not have C{CONTROL} permissions on the given L{Namespace} or L{Tag}. @return: A C{Deferred} that will fire with a C{None} if the operation was successful. """ path = path.decode('utf-8') try: operation = getOperation(category, action) except KeyError as error: session.log.exception(error) error = TBadRequest('Action %r not possible on category %r.' % (action, category)) return defer.fail(error) policy = policyAndExceptions.policy if policy not in ('open', 'closed'): return defer.fail(TInvalidPolicy()) policy = Policy.OPEN if policy == 'open' else Policy.CLOSED exceptions = policyAndExceptions.exceptions def run(): permissions = SecurePermissionAPI(session.auth.user) try: permissions.set([(path, operation, policy, exceptions)]) except UnknownPathError as error: session.log.exception(error) unknownPath = error.paths[0] if operation in Operation.TAG_OPERATIONS: raise TNonexistentTag(unknownPath.encode('utf-8')) if operation in Operation.NAMESPACE_OPERATIONS: raise TNonexistentNamespace(unknownPath.encode('utf-8')) raise except UnknownUserError as error: # FIXME There could be more than one unknown username, but # TNoSuchUser can only be passed a single username, so we'll # only pass the first one. Ideally, we'd be able to pass all # of them. raise TNoSuchUser(error.usernames[0].encode('utf-8')) except UserNotAllowedInExceptionError as error: raise TInvalidUsername(str(error)) except PermissionDeniedError as error: session.log.exception(error) deniedPath, deniedOperation = error.pathsAndOperations[0] deniedCategory, deniedAction = getCategoryAndAction( deniedOperation) raise TPathPermissionDenied(deniedPath, deniedCategory, deniedAction) return session.transact.run(run)
def updateValuesForQueries(self, session, valuesQuerySchema): """Set L{TagValue}s that match a list of L{Query}s. @param session: The L{FluidinfoSession} for the request. @param valuesQuerySchema: L{ValuesQuerySchema} @raise TNonexistentTag: Raised if any of the L{Tag}s in the L{Query} or to set does not exist. @raise TNonexistentTag: Raised if the L{User} does not have L{Opeartion.READ_TAG_VALUE} permission on any of the L{Tag}s in the L{Query}. @raise TPathPermissionDenied: Raised if the L{User} does not have L{Operation.WRITE_TAG_VALUE} permission on any of the L{Tag}s to set. @raise TParseError: Raised if the L{Query} can't be parsed. """ # Parse queries in the reactor thread, the query parser is not thread # safe. valuesByQuery = {} for query, tagsAndValues in valuesQuerySchema.queryItems: try: parsedQuery = parseQuery(query) except QueryParseError as error: session.log.exception(error) return fail(TParseError(query, error.message)) except IllegalQueryError as error: return fail(TBadRequest(str(error))) valuesByQuery[parsedQuery] = tagsAndValues def run(): objects = SecureObjectAPI(session.auth.user) try: searchQueries = objects.search(valuesByQuery.keys()) except UnknownPathError as error: session.log.exception(error) unknownPath = error.paths[0] raise TNonexistentTag(unknownPath.encode('utf-8')) except PermissionDeniedError as error: session.log.exception(error) path_, operation = error.pathsAndOperations[0] if operation == Operation.CREATE_OBJECT: raise TUnauthorized() else: raise TNonexistentTag(path_) # Run queries. try: with session.timer.track('index-search'): result = blockingCallFromThread(reactor, searchQueries.get) except SearchError as error: session.log.exception(error) raise TParseError(query, error.message) # Build a result set from the searches. values = {} for parsedQuery, objectIDs in result.iteritems(): for objectID in objectIDs: for tagAndValue in valuesByQuery[parsedQuery]: value = guessValue(tagAndValue.value) # FIXME: this code sucks, but I rather not having # to modify guessValue to return a list, as that # would break other code. # Hopefully, we'll be able to remove this pretty # soon. if isinstance(value, list): value = [item.decode('utf-8') for item in value] if objectID not in values: values[objectID] = {} values[objectID][tagAndValue.path] = value # Update values. if values: tagValues = SecureTagValueAPI(session.auth.user) try: result = tagValues.set(values) except UnknownPathError as error: session.log.exception(error) path = error.paths[0] raise TNonexistentTag(path.encode('utf-8')) except MalformedPathError as error: # FIXME: Modify MalformedPathError to have a path field. raise TInvalidPath(str(error).encode('utf-8')) except PermissionDeniedError as error: session.log.exception(error) path_, operation = error.pathsAndOperations[0] category, action = getCategoryAndAction(operation) raise TPathPermissionDenied(category, action, path_) return session.transact.run(run)
def getValuesForQuery(self, session, query, tags=None): """Get L{TagValue}s that match a query. Existence checks are performed for L{Tag.path}s specified in the L{Query}, but not in the return list. If requested tags don't exist we treat them as having no matches. @param session: The L{FluidinfoSession} for the request. @param query: The query to resolve. @param tags: Optionally, the sequence of L{Tag.path}s to retrieve values for. @raise TNonexistentTag: Raised if L{Tag}s in the L{Query} don't exist, or if the L{User} doesn't have L{Operation.READ_TAG_VALUE} permission on all L{Tag}s in the query. @raise TParseError: Raised if the L{Query} can't be parsed. @return: A L{Deferred} that will fire with a C{dict} that maps object IDs to L{Tag.path}s with L{TagValue}s, matching the following format:: {'results': { 'id': {<object-id>: {<path>: {'value': <contents>}, <path>: {'value-type': <contents>, 'size': <size>}}}}} We only return the value type and the size for binary L{TagValue}s since JSON doesn't support binary strings. """ try: parsedQuery = parseQuery(query.decode('utf-8')) except QueryParseError as error: session.log.exception(error) return fail(TParseError(query, error.message)) except IllegalQueryError as error: return fail(TBadRequest(str(error))) if tags is not None: tags = [tag.decode('utf-8') for tag in tags] def run(): tagValues = SecureTagValueAPI(session.auth.user) objects = SecureObjectAPI(session.auth.user) objectIDs = self._resolveQuery(session, objects, parsedQuery) if not objectIDs: return dumps({'results': {'id': {}}}) try: values = tagValues.get(objectIDs, tags) except UnknownPathError as error: # One or more of the requested return Tag's doesn't exist. # We'll filter them out and try again because we don't want to # fail the request just because of a missing tag. filteredTags = set(tags) - set(error.paths) if not filteredTags: return dumps({'results': {'id': {}}}) values = tagValues.get(objectIDs, filteredTags) except PermissionDeniedError as error: session.log.exception(error) path_, operation = error.pathsAndOperations[0] raise TNonexistentTag(path_) valuesByObjectID = {} for objectID, tagPaths in values.iteritems(): for tagPath, tagValue in tagPaths.iteritems(): value = tagValue.value if isinstance(value, dict): size = len(value[u'contents']) mimeType = value[u'mime-type'] value = {u'value-type': mimeType, u'size': size} elif isinstance(value, UUID): value = {'value': str(value)} else: value = {'value': value} value['updated-at'] = tagValue.creationTime.isoformat() value['username'] = tagValue.creator.username objectID = str(objectID) valuesByObjectID.setdefault(objectID, {})[tagPath] = value result = {'results': {'id': valuesByObjectID}} return dumps(result) return session.transact.run(run)