def get_all(self, requester_user=None, limit=None, **raw_filters): """Retrieve multiple Inquiries Handles requests: GET /inquiries/ """ raw_inquiries = super(InquiriesController, self)._get_all( limit=limit, raw_filters={ 'status': action_constants.LIVEACTION_STATUS_PENDING, 'runner': INQUIRY_RUNNER }, requester_user=requester_user ) # Since "model" is set to InquiryAPI (for good reasons), _get_all returns a list of # InquiryAPI instances, already converted to JSON. So in order to convert these to # InquiryResponseAPI instances, we first have to convert raw_inquiries.body back to # a list of dicts, and then individually convert these to InquiryResponseAPI instances inquiries = [InquiryResponseAPI.from_model(raw_inquiry, skip_db=True) for raw_inquiry in json.loads(raw_inquiries.body)] # Repackage into Response with correct headers resp = Response(json=inquiries) resp.headers['X-Total-Count'] = raw_inquiries.headers['X-Total-Count'] if limit: resp.headers['X-Limit'] = str(limit) return resp
def get_all(self, requester_user, show_secrets=None, limit=None, offset=0): """ List all keys. Handles requests: GET /apikeys/ """ mask_secrets = self._get_mask_secrets(show_secrets=show_secrets, requester_user=requester_user) limit = resource.validate_limit_query_param(limit, requester_user=requester_user) try: api_key_dbs = ApiKey.get_all(limit=limit, offset=offset) api_keys = [ApiKeyAPI.from_model(api_key_db, mask_secrets=mask_secrets) for api_key_db in api_key_dbs] except OverflowError: msg = 'Offset "%s" specified is more than 32 bit int' % (offset) raise ValueError(msg) resp = Response(json=api_keys) resp.headers['X-Total-Count'] = str(api_key_dbs.count()) if limit: resp.headers['X-Limit'] = str(limit) return resp
def get_one(self, ref_or_id, requester_user): """ Outputs the file associated with action entry_point Handles requests: GET /actions/views/entry_point/1 """ LOG.info('GET /actions/views/entry_point with ref_or_id=%s', ref_or_id) action_db = self._get_by_ref_or_id(ref_or_id=ref_or_id) permission_type = PermissionType.ACTION_VIEW rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=action_db, permission_type=permission_type) pack = getattr(action_db, 'pack', None) entry_point = getattr(action_db, 'entry_point', None) abs_path = utils.get_entry_point_abs_path(pack, entry_point) if not abs_path: raise StackStormDBObjectNotFoundError('Action ref_or_id=%s has no entry_point to output' % ref_or_id) with codecs.open(abs_path, 'r') as fp: content = fp.read() # Ensure content is utf-8 if isinstance(content, six.binary_type): content = content.decode('utf-8') try: content_type = mimetypes.guess_type(abs_path)[0] except Exception: content_type = None # Special case if /etc/mime.types doesn't contain entry for yaml, py if not content_type: _, extension = os.path.splitext(abs_path) if extension in ['.yaml', '.yml']: content_type = 'application/x-yaml' elif extension in ['.py']: content_type = 'application/x-python' else: content_type = 'text/plain' response = Response() response.headers['Content-Type'] = content_type response.text = content return response
def post(self, pack_install_request, requester_user=None): parameters = { "packs": pack_install_request.packs, } if pack_install_request.force: parameters["force"] = True if pack_install_request.skip_dependencies: parameters["skip_dependencies"] = True if not requester_user: requester_user = UserDB(name=cfg.CONF.system_user.user) new_liveaction_api = LiveActionCreateAPI( action="packs.install", parameters=parameters, user=requester_user.name ) execution_resp = self._handle_schedule_execution( liveaction_api=new_liveaction_api, requester_user=requester_user ) exec_id = PackAsyncAPI(execution_id=execution_resp.json["id"]) return Response(json=exec_id, status=http_client.ACCEPTED)
def post(self, pack_install_request, requester_user=None): parameters = { 'packs': pack_install_request.packs, 'python3': pack_install_request.python3 } if pack_install_request.force: parameters['force'] = True if pack_install_request.skip_dependencies: parameters['skip_dependencies'] = True if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) new_liveaction_api = LiveActionCreateAPI(action='packs.install', parameters=parameters, user=requester_user.name) execution_resp = self._handle_schedule_execution( liveaction_api=new_liveaction_api, requester_user=requester_user) exec_id = PackAsyncAPI(execution_id=execution_resp.json['id']) return Response(json=exec_id, status=http_client.ACCEPTED)
def post(self, rule, requester_user): """ Create a new rule. Handles requests: POST /rules/ """ permission_type = PermissionType.RULE_CREATE rbac_utils.assert_user_has_resource_api_permission(user_db=requester_user, resource_api=rule, permission_type=permission_type) if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) # Validate that the authenticated user is admin if user query param is provided user = requester_user.name assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user, user=user) if not hasattr(rule, 'context'): rule.context = dict() rule.context['user'] = user try: rule_db = RuleAPI.to_model(rule) LOG.debug('/rules/ POST verified RuleAPI and formulated RuleDB=%s', rule_db) # Check referenced trigger and action permissions # Note: This needs to happen after "to_model" call since to_model performs some # validation (trigger exists, etc.) assert_user_has_rule_trigger_and_action_permission(user_db=requester_user, rule_api=rule) rule_db = Rule.add_or_update(rule_db) # After the rule has been added modify the ref_count. This way a failure to add # the rule due to violated constraints will have no impact on ref_count. increment_trigger_ref_count(rule_api=rule) except (ValidationError, ValueError) as e: LOG.exception('Validation failed for rule data=%s.', rule) abort(http_client.BAD_REQUEST, str(e)) return except (ValueValidationException, jsonschema.ValidationError) as e: LOG.exception('Validation failed for rule data=%s.', rule) abort(http_client.BAD_REQUEST, str(e)) return except TriggerDoesNotExistException as e: msg = ('Trigger "%s" defined in the rule does not exist in system or it\'s missing ' 'required "parameters" attribute' % (rule.trigger['type'])) LOG.exception(msg) abort(http_client.BAD_REQUEST, msg) return extra = {'rule_db': rule_db} LOG.audit('Rule created. Rule.id=%s' % (rule_db.id), extra=extra) rule_api = RuleAPI.from_model(rule_db) return Response(json=rule_api, status=exc.HTTPCreated.code)
def _get_one(self, ref_or_id, requester_user, permission_type, exclude_fields=None, from_model_kwargs=None): try: instance = self._get_by_ref_or_id(ref_or_id=ref_or_id, exclude_fields=exclude_fields) except Exception as e: LOG.exception(e.message) abort(http_client.NOT_FOUND, e.message) return if permission_type: rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=instance, permission_type=permission_type) from_model_kwargs = from_model_kwargs or {} from_model_kwargs.update(self.from_model_kwargs) result = self.resource_model_filter(model=self.model, instance=instance, requester_user=requester_user, **from_model_kwargs) if not result: LOG.debug('Not returning the result because RBAC resource isolation is enabled and ' 'current user doesn\'t match the resource user') raise ResourceAccessDeniedPermissionIsolationError(user_db=requester_user, resource_api_or_db=instance, permission_type=permission_type) if result and self.include_reference: pack = getattr(result, 'pack', None) name = getattr(result, 'name', None) result.ref = ResourceReference(pack=pack, name=name).ref return Response(json=result)
def delete(self, triggertype_ref_or_id): """ Delete a triggertype. Handles requests: DELETE /triggertypes/1 DELETE /triggertypes/pack.name """ LOG.info('DELETE /triggertypes/ with ref_or_id=%s', triggertype_ref_or_id) triggertype_db = self._get_by_ref_or_id(ref_or_id=triggertype_ref_or_id) triggertype_id = triggertype_db.id try: validate_not_part_of_system_pack(triggertype_db) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) try: TriggerType.delete(triggertype_db) except Exception as e: LOG.exception('Database delete encountered exception during delete of id="%s". ', triggertype_id) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return else: extra = {'triggertype': triggertype_db} LOG.audit('TriggerType deleted. TriggerType.id=%s' % (triggertype_db.id), extra=extra) if not triggertype_db.parameters_schema: TriggerTypeController._delete_shadow_trigger(triggertype_db) return Response(status=http_client.NO_CONTENT)
def _get_one(self, ref_or_id, requester_user, permission_type, exclude_fields=None, from_model_kwargs=None): try: instance = self._get_by_ref_or_id(ref_or_id=ref_or_id, exclude_fields=exclude_fields) except Exception as e: LOG.exception(e.message) abort(http_client.NOT_FOUND, e.message) return if permission_type: rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=instance, permission_type=permission_type) from_model_kwargs = from_model_kwargs or {} from_model_kwargs.update(self.from_model_kwargs) result = self.model.from_model(instance, **from_model_kwargs) if result and self.include_reference: pack = getattr(result, 'pack', None) name = getattr(result, 'name', None) result.ref = ResourceReference(pack=pack, name=name).ref return Response(json=result)
def get(self, id, attribute, requester_user): """ Retrieve a particular attribute for the provided action execution. Handles requests: GET /executions/<id>/attribute/<attribute name> :rtype: ``dict`` """ fields = [attribute, 'action__pack', 'action__uid'] try: fields = self._validate_exclude_fields(fields) except ValueError: valid_attributes = ', '.join(ActionExecutionsControllerMixin.valid_exclude_attributes) msg = ('Invalid attribute "%s" specified. Valid attributes are: %s' % (attribute, valid_attributes)) raise ValueError(msg) action_exec_db = self.access.impl.model.objects.filter(id=id).only(*fields).get() permission_type = PermissionType.EXECUTION_VIEW rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=action_exec_db, permission_type=permission_type) result = getattr(action_exec_db, attribute, None) return Response(json=result, status=http_client.OK)
def post(self, instance, requester_user): """ Create a new policy. Handles requests: POST /policies/ """ permission_type = PermissionType.POLICY_CREATE rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_api_permission( user_db=requester_user, resource_api=instance, permission_type=permission_type) op = 'POST /policies/' db_model = self.model.to_model(instance) LOG.debug('%s verified object: %s', op, db_model) db_model = self.access.add_or_update(db_model) LOG.debug('%s created object: %s', op, db_model) LOG.audit('Policy created. Policy.id=%s' % (db_model.id), extra={'policy_db': db_model}) exec_result = self.model.from_model(db_model) return Response(json=exec_result, status=http_client.CREATED)
def _get_one(self, ref_or_id, requester_user, permission_type, exclude_fields=None, include_fields=None, from_model_kwargs=None): try: instance = self._get_by_ref_or_id(ref_or_id=ref_or_id, exclude_fields=exclude_fields, include_fields=include_fields) except Exception as e: LOG.exception(str(e)) abort(http_client.NOT_FOUND, str(e)) return if permission_type: rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=instance, permission_type=permission_type) # Perform resource isolation check (if supported) from_model_kwargs = from_model_kwargs or {} from_model_kwargs.update(self.from_model_kwargs) result = self.resource_model_filter(model=self.model, instance=instance, requester_user=requester_user, **from_model_kwargs) if not result: LOG.debug('Not returning the result because RBAC resource isolation is enabled and ' 'current user doesn\'t match the resource user') raise ResourceAccessDeniedPermissionIsolationError(user_db=requester_user, resource_api_or_db=instance, permission_type=permission_type) return Response(json=result)
def post(self, hook, webhook_body_api, headers, requester_user): body = webhook_body_api.data permission_type = PermissionType.WEBHOOK_SEND rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=WebhookDB(name=hook), permission_type=permission_type) headers = self._get_headers_as_dict(headers) # If webhook contains a trace-tag use that else create create a unique trace-tag. trace_context = self._create_trace_context(trace_tag=headers.pop( TRACE_TAG_HEADER, None), hook=hook) if hook == 'st2' or hook == 'st2/': # When using st2 or system webhook, body needs to always be a dict if not isinstance(body, dict): type_string = get_json_type_for_python_value(body) msg = ('Webhook body needs to be an object, got: %s' % (type_string)) raise ValueError(msg) trigger = body.get('trigger', None) payload = body.get('payload', None) if not trigger: msg = 'Trigger not specified.' return abort(http_client.BAD_REQUEST, msg) self._trigger_dispatcher_service.dispatch_with_context( trigger=trigger, payload=payload, trace_context=trace_context, throw_on_validation_error=True) else: if not self._is_valid_hook(hook): self._log_request('Invalid hook.', headers, body) msg = 'Webhook %s not registered with st2' % hook return abort(http_client.NOT_FOUND, msg) triggers = self._hooks.get_triggers_for_hook(hook) payload = {} payload['headers'] = headers payload['body'] = body # Dispatch trigger instance for each of the trigger found for trigger_dict in triggers: # TODO: Instead of dispatching the whole dict we should just # dispatch TriggerDB.ref or similar self._trigger_dispatcher_service.dispatch_with_context( trigger=trigger_dict, payload=payload, trace_context=trace_context, throw_on_validation_error=True) return Response(json=body, status=http_client.ACCEPTED)
def delete(self, ref_or_id, requester_user): """ Delete an action alias. Handles requests: DELETE /actionalias/1 """ action_alias_db = self._get_by_ref_or_id(ref_or_id=ref_or_id) LOG.debug('DELETE /actionalias/ lookup with id=%s found object: %s', ref_or_id, action_alias_db) permission_type = PermissionType.ACTION_ALIAS_DELETE rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=action_alias_db, permission_type=permission_type) try: ActionAlias.delete(action_alias_db) except Exception as e: LOG.exception( 'Database delete encountered exception during delete of id="%s".', ref_or_id) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return extra = {'action_alias_db': action_alias_db} LOG.audit('Action alias deleted. ActionAlias.id=%s.' % (action_alias_db.id), extra=extra) return Response(status=http_client.NO_CONTENT)
def post(self, triggertype): """ Create a new triggertype. Handles requests: POST /triggertypes/ """ try: triggertype_db = TriggerTypeAPI.to_model(triggertype) triggertype_db = TriggerType.add_or_update(triggertype_db) except (ValidationError, ValueError) as e: LOG.exception("Validation failed for triggertype data=%s.", triggertype) abort(http_client.BAD_REQUEST, six.text_type(e)) return else: extra = {"triggertype_db": triggertype_db} LOG.audit( "TriggerType created. TriggerType.id=%s" % (triggertype_db.id), extra=extra, ) if not triggertype_db.parameters_schema: TriggerTypeController._create_shadow_trigger(triggertype_db) triggertype_api = TriggerTypeAPI.from_model(triggertype_db) return Response(json=triggertype_api, status=http_client.CREATED)
def test_install(self, _handle_schedule_execution): user_db = self.users['system_admin'] self.use_user(user_db) _handle_schedule_execution.return_value = Response(json={'id': '123'}) payload = {'packs': ['some']} resp = self.app.post_json('/v1/packs/install', payload) self.assertEqual(resp.status_int, 202) self.assertEqual(resp.json, {'execution_id': '123'}) # Verify created execution correctly used the user which performed the API operation call_kwargs = _handle_schedule_execution.call_args[1] self.assertEqual(call_kwargs['requester_user'], user_db) self.assertEqual(call_kwargs['liveaction_api'].user, user_db.name) # Try with a different user user_db = self.users['admin'] self.use_user(user_db) resp = self.app.post_json('/v1/packs/install', payload) self.assertEqual(resp.status_int, 202) self.assertEqual(resp.json, {'execution_id': '123'}) # Verify created execution correctly used the user which performed the API operation call_kwargs = _handle_schedule_execution.call_args[1] self.assertEqual(call_kwargs['requester_user'], user_db) self.assertEqual(call_kwargs['liveaction_api'].user, user_db.name)
def delete(self, ref_or_id): """ Delete a policy. Handles requests: POST /policies/1?_method=delete DELETE /policies/1 DELETE /policies/mypack.mypolicy """ op = 'DELETE /policies/%s/' % ref_or_id db_model = self._get_by_ref_or_id(ref_or_id=ref_or_id) LOG.debug('%s found object: %s', op, db_model) try: validate_not_part_of_system_pack(db_model) except ValueValidationException as e: LOG.exception('%s unable to delete object from system pack.', op) abort(http_client.BAD_REQUEST, str(e)) try: self.access.delete(db_model) except Exception as e: LOG.exception('%s unable to delete object: %s', op, db_model) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return LOG.debug('%s deleted object: %s', op, db_model) LOG.audit('Policy deleted. Policy.id=%s' % (db_model.id), extra={'policy_db': db_model}) # return None return Response(status=http_client.NO_CONTENT)
def put(self, instance, ref_or_id): op = 'PUT /policies/%s/' % ref_or_id db_model = self._get_by_ref_or_id(ref_or_id=ref_or_id) LOG.debug('%s found object: %s', op, db_model) db_model_id = db_model.id try: validate_not_part_of_system_pack(db_model) except ValueValidationException as e: LOG.exception('%s unable to update object from system pack.', op) abort(http_client.BAD_REQUEST, str(e)) if not getattr(instance, 'pack', None): instance.pack = db_model.pack try: db_model = self.model.to_model(instance) db_model.id = db_model_id db_model = self.access.add_or_update(db_model) except (ValidationError, ValueError) as e: LOG.exception('%s unable to update object: %s', op, db_model) abort(http_client.BAD_REQUEST, str(e)) return LOG.debug('%s updated object: %s', op, db_model) LOG.audit('Policy updated. Policy.id=%s' % (db_model.id), extra={'policy_db': db_model}) exec_result = self.model.from_model(db_model) return Response(json=exec_result, status=http_client.OK)
def post(self, action_alias, requester_user): """ Create a new ActionAlias. Handles requests: POST /actionalias/ """ permission_type = PermissionType.ACTION_ALIAS_CREATE rbac_utils.assert_user_has_resource_api_permission( user_db=requester_user, resource_api=action_alias, permission_type=permission_type) try: action_alias_db = ActionAliasAPI.to_model(action_alias) LOG.debug( '/actionalias/ POST verified ActionAliasAPI and formulated ActionAliasDB=%s', action_alias_db) action_alias_db = ActionAlias.add_or_update(action_alias_db) except (ValidationError, ValueError, ValueValidationException) as e: LOG.exception('Validation failed for action alias data=%s.', action_alias) abort(http_client.BAD_REQUEST, str(e)) return extra = {'action_alias_db': action_alias_db} LOG.audit('Action alias created. ActionAlias.id=%s' % (action_alias_db.id), extra=extra) action_alias_api = ActionAliasAPI.from_model(action_alias_db) return Response(json=action_alias_api, status=http_client.CREATED)
def delete(self, rule_ref_or_id, requester_user): """ Delete a rule. Handles requests: DELETE /rules/1 """ rule_db = self._get_by_ref_or_id(ref_or_id=rule_ref_or_id) permission_type = PermissionType.RULE_DELETE rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=rule_db, permission_type=permission_type) LOG.debug('DELETE /rules/ lookup with id=%s found object: %s', rule_ref_or_id, rule_db) try: Rule.delete(rule_db) except Exception as e: LOG.exception( 'Database delete encountered exception during delete of id="%s".', rule_ref_or_id) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return # use old_rule_db for cleanup. cleanup_trigger_db_for_rule(rule_db) extra = {'rule_db': rule_db} LOG.audit('Rule deleted. Rule.id=%s.' % (rule_db.id), extra=extra) return Response(status=http_client.NO_CONTENT)
def test_uninstall(self, _handle_schedule_execution): _handle_schedule_execution.return_value = Response(json={"id": "123"}) payload = {"packs": ["some"]} resp = self.app.post_json("/v1/packs/uninstall", payload) self.assertEqual(resp.status_int, 202) self.assertEqual(resp.json, {"execution_id": "123"})
def make_response(): listener = get_listener() app_iter = format( listener.generator(events=events, action_refs=action_refs, execution_ids=execution_ids)) res = Response(content_type='text/event-stream', app_iter=app_iter) return res
def test_install_with_force_parameter(self, _handle_schedule_execution): _handle_schedule_execution.return_value = Response(json={'id': '123'}) payload = {'packs': ['some'], 'force': True} resp = self.app.post_json('/v1/packs/install', payload) self.assertEqual(resp.status_int, 202) self.assertEqual(resp.json, {'execution_id': '123'})
def test_install_with_force_parameter(self, _handle_schedule_execution): _handle_schedule_execution.return_value = Response(json={"id": "123"}) payload = {"packs": ["some"], "force": True} resp = self.app.post_json("/v1/packs/install", payload) self.assertEqual(resp.status_int, 202) self.assertEqual(resp.json, {"execution_id": "123"})
def post(self, def_yaml): result = self.validator.validate(def_yaml) for error in result: if not error.get('path', None): error['path'] = '' return Response(json=result)
def test_uninstall(self, _handle_schedule_execution): _handle_schedule_execution.return_value = Response(json={'id': '123'}) payload = {'packs': ['some']} resp = self.app.post_json('/v1/packs/uninstall', payload) self.assertEqual(resp.status_int, 202) self.assertEqual(resp.json, {'execution_id': '123'})
def _schedule_execution(self, liveaction, user=None, context_string=None, show_secrets=False): # Initialize execution context if it does not exist. if not hasattr(liveaction, 'context'): liveaction.context = dict() liveaction.context['user'] = user LOG.debug('User is: %s' % liveaction.context['user']) # Retrieve other st2 context from request header. if context_string: context = try_loads(context_string) if not isinstance(context, dict): raise ValueError( 'Unable to convert st2-context from the headers into JSON.' ) liveaction.context.update(context) # Schedule the action execution. liveaction_db = LiveActionAPI.to_model(liveaction) liveaction_db, actionexecution_db = action_service.create_request( liveaction_db) action_db = action_utils.get_action_by_ref(liveaction_db.action) runnertype_db = action_utils.get_runnertype_by_name( action_db.runner_type['name']) try: liveaction_db.parameters = param_utils.render_live_params( runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters, liveaction_db.context) except ParamException: # By this point the execution is already in the DB therefore need to mark it failed. _, e, tb = sys.exc_info() action_service.update_status(liveaction=liveaction_db, new_status=LIVEACTION_STATUS_FAILED, result={ 'error': str(e), 'traceback': ''.join( traceback.format_tb(tb, 20)) }) # Might be a good idea to return the actual ActionExecution rather than bubble up # the execption. raise ValueValidationException(str(e)) liveaction_db = LiveAction.add_or_update(liveaction_db, publish=False) _, actionexecution_db = action_service.publish_request( liveaction_db, actionexecution_db) execution_api = ActionExecutionAPI.from_model( actionexecution_db, mask_secrets=(not show_secrets)) return Response(json=execution_api, status=http_client.CREATED)
def post(self, api_key_api, requester_user): """ Create a new entry. """ permission_type = PermissionType.API_KEY_CREATE rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_api_permission( user_db=requester_user, resource_api=api_key_api, permission_type=permission_type, ) api_key_db = None api_key = None try: if not getattr(api_key_api, "user", None): if requester_user: api_key_api.user = requester_user.name else: api_key_api.user = cfg.CONF.system_user.user try: User.get_by_name(api_key_api.user) except StackStormDBObjectNotFoundError: user_db = UserDB(name=api_key_api.user) User.add_or_update(user_db) extra = {"username": api_key_api.user, "user": user_db} LOG.audit('Registered new user "%s".' % (api_key_api.user), extra=extra) # If key_hash is provided use that and do not create a new key. The assumption # is user already has the original api-key if not getattr(api_key_api, "key_hash", None): api_key, api_key_hash = auth_util.generate_api_key_and_hash() # store key_hash in DB api_key_api.key_hash = api_key_hash api_key_db = ApiKey.add_or_update(ApiKeyAPI.to_model(api_key_api)) except (ValidationError, ValueError) as e: LOG.exception("Validation failed for api_key data=%s.", api_key_api) abort(http_client.BAD_REQUEST, six.text_type(e)) extra = {"api_key_db": api_key_db} LOG.audit("ApiKey created. ApiKey.id=%s" % (api_key_db.id), extra=extra) api_key_create_response_api = ApiKeyCreateResponseAPI.from_model( api_key_db) # Return real api_key back to user. A one-way hash of the api_key is stored in the DB # only the real value only returned at create time. Also, no masking of key here since # the user needs to see this value atleast once. api_key_create_response_api.key = api_key return Response(json=api_key_create_response_api, status=http_client.CREATED)
def make_response(): app_iter = itertools.chain(existing_output_iter(), new_output_iter()) res = Response(headerlist=[("X-Accel-Buffering", "no"), ('Cache-Control', 'no-cache'), ("Content-Type", "text/event-stream; charset=UTF-8")], app_iter=app_iter) return res
def __call__(self, environ, start_response): request = Request(environ) def custom_start_response(status, headers, exc_info=None): headers = ResponseHeaders(headers) origin = request.headers.get('Origin') origins = set(cfg.CONF.api.allow_origin) # Build a list of the default allowed origins public_api_url = cfg.CONF.auth.api_url # Default gulp development server WebUI URL origins.add('http://127.0.0.1:3000') # By default WebUI simple http server listens on 8080 origins.add('http://localhost:8080') origins.add('http://127.0.0.1:8080') if public_api_url: # Public API URL origins.add(public_api_url) if origin: if '*' in origins: origin_allowed = '*' else: # See http://www.w3.org/TR/cors/#access-control-allow-origin-response-header origin_allowed = origin if origin in origins else 'null' else: origin_allowed = list(origins)[0] methods_allowed = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] request_headers_allowed = [ 'Content-Type', 'Authorization', HEADER_ATTRIBUTE_NAME, HEADER_API_KEY_ATTRIBUTE_NAME, REQUEST_ID_HEADER ] response_headers_allowed = [ 'Content-Type', 'X-Limit', 'X-Total-Count', REQUEST_ID_HEADER ] headers['Access-Control-Allow-Origin'] = origin_allowed headers['Access-Control-Allow-Methods'] = ','.join(methods_allowed) headers['Access-Control-Allow-Headers'] = ','.join( request_headers_allowed) headers['Access-Control-Expose-Headers'] = ','.join( response_headers_allowed) return start_response(status, headers._items, exc_info) try: return self.app(environ, custom_start_response) except NotFoundException: if request.method != 'options': raise return Response()(environ, custom_start_response)
def delete(self, name, requester_user, scope=FULL_SYSTEM_SCOPE, user=None): """ Delete the key value pair. Handles requests: DELETE /keys/1 """ if not scope: scope = FULL_SYSTEM_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = user or requester_user.name # Validate that the authenticated user is admin if user query param is provided rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_is_admin_if_user_query_param_is_provided( user_db=requester_user, user=user, require_rbac=True) key_ref = get_key_reference(scope=scope, name=name, user=user) lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope) # Note: We use lock to avoid a race with self._coordinator.get_lock(lock_name): from_model_kwargs = {"mask_secrets": True} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs) kvp_db = KeyValuePairAPI.to_model(kvp_api) LOG.debug( "DELETE /keys/ lookup with scope=%s name=%s found object: %s", scope, name, kvp_db, ) try: KeyValuePair.delete(kvp_db) except Exception as e: LOG.exception( "Database delete encountered exception during " 'delete of name="%s". ', name, ) abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e)) return extra = {"kvp_db": kvp_db} LOG.audit("KeyValuePair deleted. KeyValuePair.id=%s" % (kvp_db.id), extra=extra) return Response(status=http_client.NO_CONTENT)
def __call__(self, environ, start_response): # The middleware sets a number of headers that helps prevent a range of attacks in browser # environment. It also handles OPTIONS requests used by browser as pre-flight check before # the potentially insecure request is made. An absence of this headers on the response will # prevent the error from ever reaching the JS layer of client-side code making it impossible # to process the response or provide a human-friendly error message. Order is not important # as long at this condition is met and headers not get overridden by another middleware # higher up the call stack. request = Request(environ) def custom_start_response(status, headers, exc_info=None): headers = ResponseHeaders(headers) origin = request.headers.get('Origin') origins = set(cfg.CONF.api.allow_origin) # Build a list of the default allowed origins public_api_url = cfg.CONF.auth.api_url # Default gulp development server WebUI URL origins.add('http://127.0.0.1:3000') # By default WebUI simple http server listens on 8080 origins.add('http://localhost:8080') origins.add('http://127.0.0.1:8080') if public_api_url: # Public API URL origins.add(public_api_url) if origin: if '*' in origins: origin_allowed = '*' else: # See http://www.w3.org/TR/cors/#access-control-allow-origin-response-header origin_allowed = origin if origin in origins else 'null' else: origin_allowed = list(origins)[0] methods_allowed = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] request_headers_allowed = ['Content-Type', 'Authorization', HEADER_ATTRIBUTE_NAME, HEADER_API_KEY_ATTRIBUTE_NAME, REQUEST_ID_HEADER] response_headers_allowed = ['Content-Type', 'X-Limit', 'X-Total-Count', REQUEST_ID_HEADER] headers['Access-Control-Allow-Origin'] = origin_allowed headers['Access-Control-Allow-Methods'] = ','.join(methods_allowed) headers['Access-Control-Allow-Headers'] = ','.join(request_headers_allowed) headers['Access-Control-Expose-Headers'] = ','.join(response_headers_allowed) return start_response(status, headers._items, exc_info) if request.method == 'OPTIONS': return Response()(environ, custom_start_response) else: return self.app(environ, custom_start_response)
def get_one(self, ref_or_id, file_path, requester_user, **kwargs): """ Outputs the content of a specific file in a pack. Handles requests: GET /packs/views/file/<pack_ref_or_id>/<file path> """ pack_db = self._get_by_ref_or_id(ref_or_id=ref_or_id) if not pack_db: msg = 'Pack with ref_or_id "%s" does not exist' % (ref_or_id) raise StackStormDBObjectNotFoundError(msg) if not file_path: raise ValueError('Missing file path') pack_ref = pack_db.ref # Note: Until list filtering is in place we don't require RBAC check for icon file permission_type = PermissionType.PACK_VIEW if file_path not in WHITELISTED_FILE_PATHS: rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=pack_db, permission_type=permission_type) normalized_file_path = get_pack_file_abs_path(pack_ref=pack_ref, file_path=file_path) if not normalized_file_path or not os.path.isfile( normalized_file_path): # Ignore references to files which don't exist on disk raise StackStormDBObjectNotFoundError('File "%s" not found' % (file_path)) file_size, file_mtime = self._get_file_stats( file_path=normalized_file_path) response = Response() if not self._is_file_changed(file_mtime, **kwargs): response.status = http_client.NOT_MODIFIED else: if file_size is not None and file_size > MAX_FILE_SIZE: msg = ('File %s exceeds maximum allowed file size (%s bytes)' % (file_path, MAX_FILE_SIZE)) raise ValueError(msg) content_type = mimetypes.guess_type(normalized_file_path)[0] or \ 'application/octet-stream' response.headers['Content-Type'] = content_type response.body = self._get_file_content( file_path=normalized_file_path) response.headers['Last-Modified'] = format_date_time(file_mtime) response.headers['ETag'] = repr(file_mtime) return response
def get_one(self, ref_or_id, file_path, requester_user, if_none_match=None, if_modified_since=None): """ Outputs the content of a specific file in a pack. Handles requests: GET /packs/views/file/<pack_ref_or_id>/<file path> """ pack_db = self._get_by_ref_or_id(ref_or_id=ref_or_id) if not pack_db: msg = 'Pack with ref_or_id "%s" does not exist' % (ref_or_id) raise StackStormDBObjectNotFoundError(msg) if not file_path: raise ValueError('Missing file path') pack_ref = pack_db.ref # Note: Until list filtering is in place we don't require RBAC check for icon file permission_type = PermissionType.PACK_VIEW if file_path not in WHITELISTED_FILE_PATHS: rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=pack_db, permission_type=permission_type) normalized_file_path = get_pack_file_abs_path(pack_ref=pack_ref, file_path=file_path) if not normalized_file_path or not os.path.isfile(normalized_file_path): # Ignore references to files which don't exist on disk raise StackStormDBObjectNotFoundError('File "%s" not found' % (file_path)) file_size, file_mtime = self._get_file_stats(file_path=normalized_file_path) response = Response() if not self._is_file_changed(file_mtime, if_none_match=if_none_match, if_modified_since=if_modified_since): response.status = http_client.NOT_MODIFIED else: if file_size is not None and file_size > MAX_FILE_SIZE: msg = ('File %s exceeds maximum allowed file size (%s bytes)' % (file_path, MAX_FILE_SIZE)) raise ValueError(msg) content_type = mimetypes.guess_type(normalized_file_path)[0] or \ 'application/octet-stream' response.headers['Content-Type'] = content_type response.body = self._get_file_content(file_path=normalized_file_path) response.headers['Last-Modified'] = format_date_time(file_mtime) response.headers['ETag'] = repr(file_mtime) return response
def _get_all(self, exclude_fields=None, include_fields=None, advanced_filters=None, sort=None, offset=0, limit=None, query_options=None, from_model_kwargs=None, raw_filters=None, requester_user=None): """ :param exclude_fields: A list of object fields to exclude. :type exclude_fields: ``list`` """ raw_filters = copy.deepcopy(raw_filters) or {} exclude_fields = exclude_fields or [] include_fields = include_fields or [] query_options = query_options if query_options else self.query_options # TODO: Why do we use comma delimited string, user can just specify # multiple values using ?sort=foo&sort=bar and we get a list back sort = sort.split(',') if sort else [] db_sort_values = [] for sort_key in sort: if sort_key.startswith('-'): direction = '-' sort_key = sort_key[1:] elif sort_key.startswith('+'): direction = '+' sort_key = sort_key[1:] else: direction = '' if sort_key not in self.supported_filters: # Skip unsupported sort key continue sort_value = direction + self.supported_filters[sort_key] db_sort_values.append(sort_value) default_sort_values = copy.copy(query_options.get('sort')) raw_filters['sort'] = db_sort_values if db_sort_values else default_sort_values # TODO: To protect us from DoS, we need to make max_limit mandatory offset = int(offset) if offset >= 2**31: raise ValueError('Offset "%s" specified is more than 32-bit int' % (offset)) limit = validate_limit_query_param(limit=limit, requester_user=requester_user) eop = offset + int(limit) if limit else None filters = {} for k, v in six.iteritems(self.supported_filters): filter_value = raw_filters.get(k, None) if not filter_value: continue value_transform_function = self.filter_transform_functions.get(k, None) value_transform_function = value_transform_function or (lambda value: value) filter_value = value_transform_function(value=filter_value) if k in ['id', 'name'] and isinstance(filter_value, list): filters[k + '__in'] = filter_value else: field_name_split = v.split('.') # Make sure filter value is a list when using "in" filter if field_name_split[-1] == 'in' and not isinstance(filter_value, (list, tuple)): filter_value = [filter_value] filters['__'.join(field_name_split)] = filter_value if advanced_filters: for token in advanced_filters.split(' '): try: [k, v] = token.split(':', 1) except ValueError: raise ValueError('invalid format for filter "%s"' % token) path = k.split('.') try: self.model.model._lookup_field(path) filters['__'.join(path)] = v except LookUpError as e: raise ValueError(str(e)) if exclude_fields and include_fields: msg = ('exclude_fields and include_fields arguments are mutually exclusive. ' 'You need to provide either one or another, but not both.') raise ValueError(msg) instances = self.access.query(exclude_fields=exclude_fields, only_fields=include_fields, **filters) if limit == 1: # Perform the filtering on the DB side instances = instances.limit(limit) from_model_kwargs = from_model_kwargs or {} from_model_kwargs.update(self.from_model_kwargs) result = self.resources_model_filter(model=self.model, instances=instances, offset=offset, eop=eop, requester_user=requester_user, **from_model_kwargs) resp = Response(json=result) resp.headers['X-Total-Count'] = str(instances.count()) if limit: resp.headers['X-Limit'] = str(limit) return resp
def process_successful_response(token): resp = Response(json=token, status=http_client.CREATED) resp.headers['X-API-URL'] = cfg.CONF.auth.api_url return resp