def paginate_get_results(get_data, offset=None, limit=None): data_length = len(get_data) if offset is None: if limit is None: return get_data else: offset = 0 if limit is None: limit = data_length else: limit = offset + limit error_json = {} if offset < 0 or offset > data_length: error_json = utils.to_json_error("Pagination index out of range", None, REST_QUERY_PARAM_OFFSET) elif limit < 0: error_json = utils.to_json_error("Pagination index out of range", None, REST_QUERY_PARAM_LIMIT) elif offset >= limit: error_json = utils.to_json_error("Pagination offset can't be equal " + "or greater than offset + limit") if error_json: return {ERROR: error_json} sliced_get_data = get_data[offset:limit] return sliced_get_data
def paginate_get_results(get_data, offset=None, limit=None): data_length = len(get_data) if offset is None: if limit is None: return get_data else: offset = 0 # limit is exclusive if limit is None: limit = data_length else: limit = offset + limit error_json = {} if offset < 0 or offset > data_length: error_json = utils.to_json_error("Pagination index out of range", None, REST_QUERY_PARAM_OFFSET) elif limit < 0: error_json = utils.to_json_error("Pagination index out of range", None, REST_QUERY_PARAM_LIMIT) elif offset >= limit: error_json = utils.to_json_error("Pagination offset can't be equal " + "or greater than offset + limit") if error_json: return {ERROR: error_json} sliced_get_data = get_data[offset:limit] return sliced_get_data
def update(self, item_id, data, current_user): """ Update user from ovsdb_users group Returns result dictionary """ # Validate json validation_result = self.schema_validator.validate_json(data, OP_UPDATE) if validation_result is not None: return {ERROR: validation_result} # Validate user data[OVSDB_SCHEMA_CONFIG]["username"] = item_id user = RestObject.from_json(data) validation_result = self.validator.validate_update(user, current_user) if validation_result is not None: return {ERROR: validation_result} # Update user username = user.configuration.username encoded_password = self.__get_encrypted_password__(user) result = {} try: cmd_result = self.__call_user_mod__(username, encoded_password) if cmd_result != 0: error_json = to_json_error("User %s not modified" % username, None, None) result = {ERROR: error_json} except KeyError: error_json = to_json_error("An error ocurred", None, None) result = {ERROR: error_json} return result
def validate_update(self, user, current_user): """ Validates required fields, username, verifies if the user exists, verifies that the user is not root and that belongs to ovsdb_user group Returns None when valid else returns error json dict """ validation_result = self.__validate_required_fields__(user, OP_UPDATE) if validation_result is not None: return validation_result username = user.configuration.username validation_result = self.__validate_username__(username) if validation_result is not None: return validation_result if user_utils.user_exists(username): # Avoid update a root user if username == "root": error_message = "Permission denied."\ "Cannot update the root user." validation_result = to_json_error(error_message, None, None) return validation_result # Avoid update users from another group if not user_utils.check_user_group(username, DEFAULT_USER_GRP): error_message = "Unknown user %s" % username validation_result = to_json_error(error_message, None, None) return validation_result else: error_message = "User %s doesn't exists." % username validation_result = to_json_error(error_message, None, None) return validation_result return None
def validate_query_args( sorting_args, filter_args, pagination_args, query_arguments, resource, schema, selector, depth, is_collection=True ): # Non-plural resources only required to validate if # sort, filter, or pagination parameters are NOT present if not is_collection: return validate_non_plural_query_args(query_arguments) # For collection resources, go ahead and # validate correctness of all parameters staging_sort_data = get_sorting_args(query_arguments, resource, schema, selector) # get_sorting_args returns a list of column # names to sort by or an ERROR dictionary if ERROR in staging_sort_data: return staging_sort_data else: sorting_args.extend(staging_sort_data) # get_filters_args returns a dictionary with # either filter->value pairs or an ERROR filter_args.update(get_filters_args(query_arguments, resource, schema, selector)) if ERROR in filter_args: return filter_args offset = None limit = None try: limit = get_query_arg(REST_QUERY_PARAM_LIMIT, query_arguments) offset = get_query_arg(REST_QUERY_PARAM_OFFSET, query_arguments) if offset is not None: offset = int(offset) if limit is not None: limit = int(limit) pagination_args[REST_QUERY_PARAM_OFFSET] = offset pagination_args[REST_QUERY_PARAM_LIMIT] = limit except: error_json = utils.to_json_error("Pagination indexes must be numbers") return {ERROR: error_json} if depth == 0 and (sorting_args or filter_args or offset is not None or limit is not None): error_json = utils.to_json_error( "Sort, filter, and pagination " + "parameters are only supported " + "for depth > 0" ) return {ERROR: error_json} return {}
def create(self, data, current_user): """ Create user at ovsdb_users group Returns result dictionary """ # Validate json validation_result = self.schema_validator.validate_json(data, OP_CREATE) if validation_result is not None: return {ERROR: validation_result} # Validate user user = RestObject.from_json(data) validation_result = self.validator.validate_create(user, current_user) if validation_result is not None: return {ERROR: validation_result} # Create user username = user.configuration.username result = {} try: """ Add the user first because the user_id is going to be used as part of the salt """ cmd_result_add = self.__call_user_add__(username) if cmd_result_add == 0: encoded_password = self.__get_encrypted_password__(user) cmd_result_mod = self.__call_user_mod__(username, encoded_password) if cmd_result_mod == 0: result = {"key": username} else: # Try to delete the added user cmd_result_del = self.__call_user_del__(username) message_error = "" if cmd_result_del == 0: message_error = "User password not set, user deleted." else: message_error = "User password not set, failed deleting "\ "user." error_json = to_json_error(message_error, None, None) result = {ERROR: error_json} else: error_json = to_json_error("User %s not added" % username, None, None) result = {ERROR: error_json} except KeyError: error_json = to_json_error("An error ocurred", None, None) result = {ERROR: error_json} return result
def _get_depth_param(query_arguments): depth = 0 depth_param = get_query_arg(REST_QUERY_PARAM_DEPTH, query_arguments) if depth_param: try: depth = int(depth_param) if depth < 0: error_json = utils.to_json_error("Depth parameter must be " + "greater or equal than zero") return {ERROR: error_json} except ValueError: error_json = utils.to_json_error("Depth parameter must " + "be a number") return {ERROR: error_json} return depth
def __validate_required_fields__(self, user, operation): """ Validate required user fields Returns None when valid else returns error json dict """ if operation == OP_CREATE and \ (not hasattr(user.configuration, "username") or user.configuration.username is None): validation_result = to_json_error(REQUIRED_FIELD_MESSAGE, None, "username") return validation_result if not hasattr(user.configuration, "password") or \ user.configuration.password is None: validation_result = to_json_error(REQUIRED_FIELD_MESSAGE, None, "password") return validation_result return None
def __validate_username__(self, username): """ Validate username using a regular expression Returns None when valid else returns error json dict """ re_result = re.match(USERNAME_REGEX, username) if not re_result or username != re_result.group(): validation_result = to_json_error("Invalid username", None, "username") return validation_result return None
def get_depth_param(query_arguments): depth = 0 depth_param = get_query_arg(REST_QUERY_PARAM_DEPTH, query_arguments) if depth_param: try: depth = int(depth_param) if depth < DEPTH_MIN_VALUE or depth > DEPTH_MAX_VALUE: error_json = utils.to_json_error("Depth parameter must be " + "greater or equal than " + str(DEPTH_MIN_VALUE) + " " + "and lower or equal than " + str(DEPTH_MAX_VALUE)) return {ERROR: error_json} except ValueError: error_json = utils.to_json_error("Depth parameter must " + "be a number") return {ERROR: error_json} return depth
def delete(self, item_id, current_user): """ Delete user from ovsdb_users group Returns result dictionary """ username = item_id validation_result = self.validator.validate_delete(username, current_user) if validation_result is not None: return {ERROR: validation_result} cmd_result = self.__call_user_del__(username) result = {} if cmd_result != 0: if cmd_result == 8: error_message = "User %s currently logged in." % username error_json = to_json_error(error_message, None, None) return {ERROR: error_json} else: error_json = to_json_error("User %s not deleted." % username, None, None) return {ERROR: error_json} return result
def validate_delete(self, username, current_user): """ This functions verifies the following: User is not root User is not the current user User belongs to ovsdb_user group User is not the last user at ovsdb_group Returns None when valid else returns error json dict """ # Avoid delete a root user if username == "root": error_message = "Permission denied." \ "Cannot remove the root user." validation_result = to_json_error(error_message, None, None) return validation_result # Avoid to delete the current user if username == current_user["username"]: error_message = "Permission denied." \ "Cannot remove the current user." validation_result = to_json_error(error_message, None, None) return validation_result # Avoid delete system users. if not user_utils.check_user_group(username, DEFAULT_USER_GRP): validation_result = to_json_error("Unknown user %s" % username, None, None) return validation_result # Check if deleting the last user from that group if user_utils.get_group_user_count(DEFAULT_USER_GRP) <= 1: validation_result = "Cannot delete the last user %s" % username validation_result = to_json_error(error_message, None, None) return validation_result return None
def get_sorting_args(query_arguments, resource, schema, selector=None): sorting_args = [] if query_arguments is not None and REST_QUERY_PARAM_SORTING in query_arguments: sorting_args = query_arguments[REST_QUERY_PARAM_SORTING] sorting_values = [] for arg in sorting_args: split_args = arg.split(",") sorting_values.extend(split_args) valid_sorting_values = [] if sorting_values: regexp = re.compile("^([\-]?)(.*)$") match_order = regexp.match(sorting_values[0]) # The parameter might include a - (indicating descending order) # prepended to the column name, default sorting is ascending # and this is represented as False in the reverse parameter # of sorted(), so here it's appended to the end of the # sorting arguments if match_order: order, value = match_order.groups() if order == "-": order = True else: order = False sorting_values[0] = value # Validate sorting keys valid_keys = _get_valid_keys(resource, schema, selector) for value in sorting_values: if value in valid_keys: valid_sorting_values.append(value) else: error_json = utils.to_json_error("Invalid sort column: %s" % value) return {ERROR: error_json} if valid_sorting_values: valid_sorting_values.append(order) return valid_sorting_values
def post(self): try: if HTTP_HEADER_CONTENT_LENGTH not in self.request.headers: raise LengthRequired # get the POST body post_data = json.loads(self.request.body) # create a new ovsdb transaction self.txn = self.ref_object.manager.get_new_transaction() # post_resource performs data verficiation, prepares and # commits the ovsdb transaction result = post.post_resource(post_data, self.resource_path, self.schema, self.txn, self.idl) status = result.status resource_uri = self.request.path + "/" + result.index if status == INCOMPLETE: self.ref_object.manager.monitor_transaction(self.txn) # on 'incomplete' state we wait until the transaction # completes with either success or failure yield self.txn.event.wait() status = self.txn.status # complete transaction self.transaction_complete(status) # set the http header to include URI self.set_header(HTTP_HEADER_LOCATION, resource_uri) except APIException as e: self.on_exception(e) except ValueError as e: self.set_status(httplib.BAD_REQUEST) self.set_header(HTTP_HEADER_CONTENT_TYPE, HTTP_CONTENT_TYPE_JSON) self.write(utils.to_json_error(e)) except Exception as e: self.on_exception(e) self.finish()
def validate_json(self, json_data, operation): # Validate Schema try: self.validator.validate(json_data) except ValidationError as e: app_log.debug("Error: %s" % e.message) field = None if e.path: field = e.path[-1] error_json = to_json_error(e.message, None, field) return error_json # Validate required categorization keys if OP_CREATE == operation or OP_UPDATE == operation: error_json = self.__validate_category_keys__(json_data) return error_json return None
def validate_create(self, user, current_user): """ Validate required fields, username and if the user exists Returns None when valid else returns error json dict """ validation_result = self.__validate_required_fields__(user, OP_CREATE) if validation_result is not None: return validation_result username = user.configuration.username validation_result = self.__validate_username__(username) if validation_result is not None: return validation_result if user_utils.user_exists(username): error_message = "User %s already exists" % username validation_result = to_json_error(error_message, None, None) return validation_result
def post(self): if HTTP_HEADER_CONTENT_LENGTH in self.request.headers: try: # get the POST body post_data = json.loads(self.request.body) # create a new ovsdb transaction self.txn = self.ref_object.manager.get_new_transaction() # post_resource performs data verficiation, prepares and # commits the ovsdb transaction result = post.post_resource(post_data, self.resource_path, self.schema, self.txn, self.idl) status = result.status if status == INCOMPLETE: self.ref_object.manager.monitor_transaction(self.txn) # on 'incomplete' state we wait until the transaction # completes with either success or failure yield self.txn.event.wait() status = self.txn.status # complete transaction self.transaction_complete(status) except APIException as e: self.on_exception(e) except ValueError as e: self.set_status(httplib.BAD_REQUEST) self.set_header(HTTP_HEADER_CONTENT_TYPE, HTTP_CONTENT_TYPE_JSON) self.write(utils.to_json_error(e)) except Exception as e: self.on_exception(e) else: self.set_status(httplib.LENGTH_REQUIRED) self.finish()
def put(self, resource_id): if resource_id: try: data = json.loads(self.request.body) result = self.controller.update(resource_id, data, self.current_user) if result is None: self.set_status(httplib.NOT_FOUND) elif self.successful_request(result): self.set_status(httplib.OK) except ValueError, e: self.set_status(httplib.BAD_REQUEST) self.set_header(HTTP_HEADER_CONTENT_TYPE, HTTP_CONTENT_TYPE_JSON) self.write(to_json_error(e)) except Exception, e: app_log.debug("Unexpected exception: %s", e) self.set_status(httplib.INTERNAL_SERVER_ERROR)
def patch(self): try: if HTTP_HEADER_CONTENT_LENGTH not in self.request.headers: raise LengthRequired # get the PATCH body update_data = json.loads(self.request.body) # create a new ovsdb transaction self.txn = self.ref_object.manager.get_new_transaction() # patch_resource performs data verification, prepares and # commits the ovsdb transaction result = yield patch.patch_resource(update_data, self.resource_path, self.schema, self.txn, self.idl, self.request.path) status = result.status if status == INCOMPLETE: self.ref_object.manager.monitor_transaction(self.txn) # on 'incomplete' state we wait until the transaction # completes with either success or failure yield self.txn.event.wait() status = self.txn.status # complete transaction self.transaction_complete(status) except APIException as e: app_log.debug("PATCH APIException") self.on_exception(e) except ValueError as e: self.set_status(httplib.BAD_REQUEST) self.set_header(HTTP_HEADER_CONTENT_TYPE, HTTP_CONTENT_TYPE_JSON) self.write(utils.to_json_error(e)) except Exception as e: app_log.debug("PATCH General Exception") self.on_exception(e) self.finish()
def verify_container_values_type(column_name, column_data, request_data): if column_data.is_list: for value in request_data: if type(value) not in column_data.type.python_types: error = "Value type mismatch in column %s" % column_name raise DataValidationFailed(error) elif column_data.is_dict: for key, value in request_data.iteritems(): # Check if request data has unknown keys for columns other than # external_ids and other_config (which are common columns and should # accept any keys). Note: common columns which do not require key # validation can be added to OVSDB_COMMON_COLUMNS array. if column_name not in OVSDB_COMMON_COLUMNS: if column_data.kvs and key not in column_data.kvs: error_json = to_json_error("Unknown key %s" % key, None, column_name) break value_type = type(value) # Values in dict must match JSON schema if value_type in column_data.value_type.python_types: # If they match, they might be strings that represent other # types, so each value must be checked if kvs type exists if value_type in ovs_types.StringType.python_types \ and column_data.kvs and key in column_data.kvs: kvs_value_type = column_data.kvs[key]['type'] converted_value = \ convert_string_to_value_by_type(value, kvs_value_type) if converted_value is None: error = "Value type mismatch for key %s in column %s"\ % (key, column_name) raise DataValidationFailed(error) else: error = "Value type mismatch for key %s in column %s"\ % (key, column_name) raise DataValidationFailed(error)
def post(self, resource_id=None): if resource_id: self.set_status(httplib.NOT_FOUND) else: try: data = json.loads(self.request.body) result = self.controller.create(data, self.current_user) if result is None: self.set_status(httplib.NOT_FOUND) elif self.successful_request(result): self.set_status(httplib.CREATED) new_uri = self.request.path + "/" + result["key"] self.set_header("Location", new_uri) except ValueError, e: self.set_status(httplib.BAD_REQUEST) self.set_header(HTTP_HEADER_CONTENT_TYPE, HTTP_CONTENT_TYPE_JSON) self.write(to_json_error(e)) except Exception, e: app_log.debug("Unexpected exception: %s", e) self.set_status(httplib.INTERNAL_SERVER_ERROR)
def get_filters_args(query_arguments, schema, resource=None): filters = {} if query_arguments is not None: valid_keys = _get_valid_keys(schema, resource) for key in query_arguments: # NOTE any new query keys should be added to this condition if key in (REST_QUERY_PARAM_LIMIT, REST_QUERY_PARAM_OFFSET, REST_QUERY_PARAM_DEPTH, REST_QUERY_PARAM_SORTING, REST_QUERY_PARAM_SELECTOR, REST_QUERY_PARAM_KEYS): continue elif key in valid_keys: filters[key] = [] for filter_ in query_arguments[key]: filters[key].extend(filter_.split(",")) else: error_json = \ utils.to_json_error("Invalid filter column: %s" % key) return {ERROR: error_json} return filters
def get_filters_args(query_arguments, schema, resource=None, selector=None): filters = {} if query_arguments is not None: valid_keys = _get_valid_keys(schema, resource, selector) for key in query_arguments: # NOTE any new query keys should be added to this condition if key in (REST_QUERY_PARAM_LIMIT, REST_QUERY_PARAM_OFFSET, REST_QUERY_PARAM_DEPTH, REST_QUERY_PARAM_SORTING, REST_QUERY_PARAM_SELECTOR, REST_QUERY_PARAM_COLUMNS): continue elif key in valid_keys: filters[key] = [] for filter_ in query_arguments[key]: filters[key].extend(filter_.split(",")) else: error_json = \ utils.to_json_error("Invalid filter column: %s" % key) return {ERROR: error_json} return filters
def validate_non_plural_query_args(query_arguments): error_json = utils.to_json_error( "Sort, filter, and pagination " + "parameters are only supported " + "for resource collections" ) error_json = {ERROR: error_json} # Check if sort or pagination parameters are present # NOTE any new query keys should be added to this condition if ( REST_QUERY_PARAM_SORTING in query_arguments or REST_QUERY_PARAM_OFFSET in query_arguments or REST_QUERY_PARAM_LIMIT in query_arguments ): return error_json # To detect if filter arguments (valid or not) are present, # remove anything else valid, and check if something was left. # At this point sort and pagination parameters should not be # present as they are validated above # NOTE any new query key valid for non-plural # resources should be added here valid_keys_count = 0 if REST_QUERY_PARAM_SELECTOR in query_arguments: valid_keys_count += 1 if REST_QUERY_PARAM_DEPTH in query_arguments: valid_keys_count += 1 invalid_keys_count = len(query_arguments) - valid_keys_count if invalid_keys_count > 0: return error_json else: return {}
def validate_non_plural_query_args(query_arguments): error_json = utils.to_json_error("Sort, filter, pagination and keys " + "parameters are only supported " + "for resource collections") error_json = {ERROR: error_json} # Check if sort or pagination parameters are present # NOTE any new query keys should be added to this condition if REST_QUERY_PARAM_SORTING in query_arguments or \ REST_QUERY_PARAM_OFFSET in query_arguments or \ REST_QUERY_PARAM_LIMIT in query_arguments or \ REST_QUERY_PARAM_KEYS in query_arguments: return error_json # To detect if filter arguments (valid or not) are present, # remove anything else valid, and check if something was left. # At this point sort and pagination parameters should not be # present as they are validated above # NOTE any new query key valid for non-plural # resources should be added here valid_keys_count = 0 if REST_QUERY_PARAM_SELECTOR in query_arguments: valid_keys_count += 1 if REST_QUERY_PARAM_DEPTH in query_arguments: valid_keys_count += 1 invalid_keys_count = len(query_arguments) - valid_keys_count if invalid_keys_count > 0: return error_json else: return {}
def __validate_category_keys__(self, json_data): if OVSDB_SCHEMA_CONFIG not in json_data: error_json = to_json_error("Missing configuration key", None, None) return error_json
def validate_query_args(sorting_args, filter_args, pagination_args, keys_args, query_arguments, schema, resource=None, depth=0, is_collection=True): # Non-plural resources only required to validate if # sort, filter, or pagination parameters are NOT present if not is_collection: return validate_non_plural_query_args(query_arguments) # For collection resources, go ahead and # validate correctness of all parameters staging_sort_data = get_sorting_args(query_arguments, schema, resource) # get_sorting_args returns a list of column # names to sort by or an ERROR dictionary if ERROR in staging_sort_data: return staging_sort_data else: sorting_args.extend(staging_sort_data) # specific column retrieval staging_keys_data = get_keys_args(query_arguments, schema, resource) # get_keys_args returns a list of column # names to show or an ERROR dictionary if ERROR in staging_keys_data: return staging_keys_data else: keys_args.extend(staging_keys_data) # get_filters_args returns a dictionary with # either filter->value pairs or an ERROR filter_args.update(get_filters_args(query_arguments, schema, resource)) if ERROR in filter_args: return filter_args offset = None limit = None try: limit = get_query_arg(REST_QUERY_PARAM_LIMIT, query_arguments) offset = get_query_arg(REST_QUERY_PARAM_OFFSET, query_arguments) if offset is not None: offset = int(offset) if limit is not None: limit = int(limit) pagination_args[REST_QUERY_PARAM_OFFSET] = offset pagination_args[REST_QUERY_PARAM_LIMIT] = limit except: error_json = utils.to_json_error("Pagination indexes must be numbers") return {ERROR: error_json} if depth == 0 and (sorting_args or filter_args or keys_args or offset is not None or limit is not None): error_json = utils.to_json_error("Sort, filter, keys and " + "pagination parameters are only " + "supported for depth > 0") return {ERROR: error_json} return {}