def toggle_ownership(request): """ Tags: ownership --- Toggle the Organization's `ownership_enabled` flag If set to True, then the ownership mappings will be taken into account when performing RBAC checks. If a user is a resource's owner, denoted in the `owned_by` field, then the user will have full access rights on that particular resource. This setting can be enabled/disabled ONLY by members of the Owners team. """ auth_context = auth_context_from_request(request) if not auth_context.is_owner(): raise UnauthorizedError('Available only to Owners') current_toggle = auth_context.owner.ownership_enabled auth_context.owner.ownership_enabled = not current_toggle auth_context.owner.save() trigger_session_update(auth_context.owner, 'org') return Response('OK', 200)
def check_auth_context(self): if self.auth_context.is_owner(): return if not self.rule.conditions: raise UnauthorizedError('Only Owners may edit global rules') for condition in self.rule.conditions: # TODO Permissions checking shouldn't be limited to machines. if not isinstance(condition, MachinesCondition): raise UnauthorizedError('Only Owners may edit rules on tags') for mid in condition.ids: try: Model = self.rule.condition_resource_cls m = Model.objects.get(id=mid, owner=self.rule.owner_id) except Model.DoesNotExist: raise NotFoundError(mid) self.auth_context.check_perm('cloud', 'read', m.cloud.id) self.auth_context.check_perm('machine', 'edit_rules', m.id)
def update_user_settings(request): """ Tags: users --- User related actions Edit name, Update password """ # SEC raise exception if not user user = user_from_request(request) auth_context = auth_context_from_request(request) params = params_from_request(request) action = params.get('action') actions = ['update_details', 'update_password'] if action not in actions: log.error("Update_user_settings bad action='%s'", action) raise BadRequestError('action') if action == 'update_details': avatar = params.get('avatar') if avatar: try: Avatar.objects.get(id=avatar) user.avatar = avatar except DoesNotExist: raise BadRequestError('Avatar does not exist') if params.get('first_name') or params.get('last_name'): user.first_name = params.get('first_name') user.last_name = params.get('last_name') elif params.get('name'): name_array = params.get('name').split(' ') if len(name_array) > 1: user.last_name = name_array[-1] user.first_name = ' '.join(name_array[:-1]) user.save() trigger_session_update(auth_context.owner, ['user']) return {} if action == 'update_password': current_password = params.get('current_password', '') password = params.get('password', '') # check if current_password provided if not current_password and (user.password and user.password != ''): raise RequiredParameterMissingError("Current password") # check if new password provided if not password: raise RequiredParameterMissingError("New password") # SEC check if current_password valid if not user.check_password(current_password): raise UnauthorizedError("Invalid current password") # set new password user.set_password(password) return {}
def check_monitoring(request): """ Tags: monitoring --- Return monitored machines and user details """ auth_context = auth_context_from_request(request) if not auth_context.is_owner(): raise UnauthorizedError() return mist.api.monitoring.methods.check_monitoring(auth_context.owner)
def get_rules(request): """ Tags: rules --- Get a list of all rules """ auth_context = auth_context_from_request(request) if not auth_context.is_owner(): raise UnauthorizedError('Restricted to Owners') return [r.as_dict() for r in Rule.objects(owner_id=auth_context.owner.id)]
def check_auth_context(self): """Perform permission checking. This method verifies the permissions of the requesting user on a rule. By default rules are edittable only by Owners to account for rules on arbitrary data. Subclasses may extend/override this method to perform more fine-grained permission checking. """ if not self.auth_context.is_owner(): raise UnauthorizedError('Only Owners may edit arbitrary rules')
def check_auth_context(self): if self.auth_context.is_owner(): return if not self.rule.selectors: raise UnauthorizedError('Only Owners may edit global rules') for selector in self.rule.selectors: if not isinstance(selector, GenericResourceSelector): raise UnauthorizedError('Only Owners may edit rules on tags') for mid in selector.ids: try: Model = self.rule.selector_resource_cls m = Model.objects.get(id=mid, owner=self.rule.owner_id) except Model.DoesNotExist: raise NotFoundError('%s %s' % (Model, mid)) read_perm = ( 'read' if self.rule._data_type_str == 'metrics' else 'read_logs' # For rules on logs. ) for perm in (read_perm, 'edit_rules'): self.auth_context.check_perm(self.resource_model_name, perm, m.id)
def transfer_ownership(self, auth_context, user): """Transfer the resource's ownership to `user` If the requesting user is not the resource's owner, then an error will be thrown, unless the requesting user is a member of the Org's Owners team. """ assert auth_context.owner == self.owner assert user in auth_context.owner.members if not self.owned_by or self.owned_by != auth_context.user: if not auth_context.is_owner(): raise UnauthorizedError('You do not own this resource') if self.owned_by: self.owned_by.get_ownership_mapper(self.owner).remove(self) self.assign_to(user, assign_creator=False)
def update_monitoring_options(request): """ Tags: monitoring --- Set global email alerts' recipients --- alerts_email: type: string description: One or more comma-separated e-mail addresses """ auth_context = auth_context_from_request(request) emails = params_from_request(request).get('alerts_email', '') if not auth_context.is_owner(): raise UnauthorizedError() return mist.api.monitoring.methods.update_monitoring_options( auth_context.owner, emails)
def toggle_rule(request): """ Tags: rules --- Enable or disable a rule Permits Owners to temporarily disable or re-enable a rule's evaluation --- rule: in: path type: string required: true description: the UUID of the rule to be updated action: in: query type: string required: true description: the action to perform (enable, disable) """ auth_context = auth_context_from_request(request) action = params_from_request(request).get('action') rule_id = request.matchdict.get('rule') if not auth_context.is_owner(): raise UnauthorizedError('Restricted to Owners') if not action: raise RequiredParameterMissingError('action') if action not in ( 'enable', 'disable', ): raise BadRequestError('Action must be one of (enable, disable)') try: rule = Rule.objects.get(owner_id=auth_context.owner.id, id=rule_id) getattr(rule.ctl, action)() except Rule.DoesNotExist: raise RuleNotFoundError() return Response('OK', 200)
def rename_rule(request): """ Tags: rules --- Rename a rule --- rule: in: path type: string required: true description: the UUID of the rule to be updated title: in: query type: string required: true description: the rule's new title """ auth_context = auth_context_from_request(request) title = params_from_request(request).get('title') rule_id = request.matchdict.get('rule') if not auth_context.is_owner(): raise UnauthorizedError('Restricted to Owners') if not title: raise RequiredParameterMissingError('title') try: rule = Rule.objects.get(owner_id=auth_context.owner.id, id=rule_id) rule.ctl.rename(title) except Rule.DoesNotExist: raise RuleNotFoundError() return Response('OK', 200)
def delete_scripts(request): """ Delete multiple scripts. Provide a list of script ids to be deleted. The method will try to delete all of them and then return a json that describes for each script id whether or not it was deleted or the not_found if the script id could not be located. If no script id was found then a 404(Not Found) response will be returned. REMOVE permission required on each script. --- script_ids: required: true type: array items: type: string name: script_id """ auth_context = auth_context_from_request(request) params = params_from_request(request) script_ids = params.get('script_ids', []) if type(script_ids) != list or len(script_ids) == 0: raise RequiredParameterMissingError('No script ids provided') # remove duplicate ids if there are any script_ids = sorted(script_ids) i = 1 while i < len(script_ids): if script_ids[i] == script_ids[i - 1]: script_ids = script_ids[:i] + script_ids[i + 1:] else: i += 1 report = {} for script_id in script_ids: try: script = Script.objects.get(owner=auth_context.owner, id=script_id, deleted=None) except me.DoesNotExist: report[script_id] = 'not_found' continue # SEC require REMOVE permission on script try: auth_context.check_perm('script', 'remove', script_id) except PolicyUnauthorizedError: report[script_id] = 'unauthorized' else: script.ctl.delete() report[script_id] = 'deleted' # /SEC # if no script id was valid raise exception if len(filter(lambda script_id: report[script_id] == 'not_found', report)) == len(script_ids): raise NotFoundError('No valid script id provided') # if user was not authorized for any script raise exception if len( filter(lambda script_id: report[script_id] == 'unauthorized', report)) == len(script_ids): raise UnauthorizedError("You don't have authorization for any of these" " scripts") return report
def update_metric(request): """ Tags: monitoring --- Update a metric configuration READ permission required on cloud EDIT_CUSTOM_METRICS permission required on machine --- metric: in: path type: string required: true cloud_id: in: query type: string machine_id: in: query type: string name: in: query type: string unit: in: query type: string """ auth_context = auth_context_from_request(request) metric_id = request.matchdict['metric'] params = params_from_request(request) name = params.get('name') unit = params.get('unit') cloud_id = params.get('cloud_id') machine_id = params.get('machine_id') # FIXME This doesn't seem right. Perhaps we should always `update_metric` # and optionally `associate_metric` if machine_id and cloud_id have been # provided. However, we already have a discrete `associate_metric` API # endpoint. if cloud_id and machine_id: try: machine = Machine.objects.get(cloud=cloud_id, machine_id=machine_id) machine_uuid = machine.id except Machine.DoesNotExist: machine_uuid = '' # Check permissions. auth_context.check_perm('cloud', 'read', cloud_id) auth_context.check_perm('machine', 'edit_custom_metrics', machine_uuid) # Associate metric. mist.api.monitoring.methods.associate_metric( auth_context.owner, cloud_id, machine_id, metric_id ) else: # FIXME Shouldn't be restricted to Owners. if not auth_context.is_owner(): raise UnauthorizedError() # Update metric information. mist.api.monitoring.methods.update_metric( auth_context.owner, metric_id, name=name, unit=unit ) return {}
def triggered(request): """ Tags: rules --- Process a trigger sent by the alert service. Based on the parameters of the request, this method will initiate actions to mitigate the conditions that triggered the rule and notify the users. --- value: type: integer required: true description: > the value that triggered the rule by exceeding the threshold incident: type: string required: true description: the incident's UUID resource: type: string required: true description: the UUID of the resource for which the rule got triggered triggered: type: integer required: true description: 0 if the specified incident got resolved/untriggered triggered_now: type: integer required: true description: | 0 in case this is not the first time the specified incident has raised an alert firing_since: type: string required: true description: | the time at which the rule raised an alert and sent a trigger to this API endpoint pending_since: type: string required: true description: | the time at which the rule evaluated to True and entered pending state. A rule can remain in pending state if a TriggerOffset has been configured. Datetime needed resolved_since: type: string required: true description: > the time at which the incident with the specified UUID resolved.\ Datetime needed """ # Do not publicly expose this API endpoint? if config.CILIA_SECRET_KEY != request.headers.get('Cilia-Secret-Key'): raise UnauthorizedError() params = params_from_request(request) keys = ( 'value', 'incident', 'resource', 'triggered', 'triggered_now', 'firing_since', 'pending_since', 'resolved_since', ) for key in keys: if key not in params: raise RequiredParameterMissingError(key) # Get the rule's UUID. # TODO rule_id = request.matchdict['rule'] rule_id = params['rule_id'] # Get resource and incidents ids. incident_id = str(params['incident']) resource_id = str(params['resource']) # Get timestamps. firing_since = str(params['firing_since']) # pending_since = str(params['pending_since']) resolved_since = str(params['resolved_since']) try: value = params['value'] value = float(value) except (TypeError, ValueError) as err: log.error('Failed to cast "%s" to float: %r', value, err) raise BadRequestError('Failed to convert %s to float' % value) def int_to_bool(param): try: return bool(int(param or 0)) except (ValueError, TypeError) as err: log.error('Failed to cast int to bool: %r', err) raise BadRequestError('Failed to convert %s to boolean' % param) # Get flags indicating whether the incident has been (just) triggered. triggered = int_to_bool(params['triggered']) triggered_now = int_to_bool(params['triggered_now']) try: machine = Machine.objects.get(id=resource_id) # missing_since=None? except Machine.DoesNotExist: raise NotFoundError('Machine with id %s does not exist' % resource_id) try: machine.cloud.owner except AttributeError: raise NotFoundError('Machine with id %s does not exist' % resource_id) if machine.cloud.deleted: raise NotFoundError('Machine with id %s does not exist' % resource_id) if machine.missing_since: raise NotFoundError('Machine with id %s does not exist' % resource_id) if machine.state == 'terminated': raise NotFoundError('Machine with id %s is terminated' % resource_id) if not machine.monitoring.hasmonitoring: raise NotFoundError('%s does not have monitoring enabled' % machine) try: rule = Rule.objects.get(id=rule_id, owner_id=machine.owner.id) except Rule.DoesNotExist: raise NotFoundError('Rule with id %s does not exist' % rule_id) # FIXME For backwards compatibility. try: timestamp = resolved_since or firing_since timestamp = int(get_datetime(timestamp).strftime('%s')) except ValueError as err: log.error('Failed to cast datetime obj to unix timestamp: %r', err) raise BadRequestError(err) if triggered_now or not triggered: notification_level = 0 else: import time notification_level = int((time.time() - timestamp) / rule.frequency.timedelta.total_seconds()) # / rule_triggered(machine, rule.title, value, triggered, timestamp, notification_level, incident_id=incident_id) return Response('OK', 200)
def triggered(request): """ Tags: rules --- Process a trigger sent by the alert service. Based on the parameters of the request, this method will initiate actions to mitigate the conditions that triggered the rule and notify the users. --- value: type: integer required: true description: > the value that triggered the rule by exceeding the threshold incident: type: string required: true description: the incident's UUID resource: type: string required: true description: the UUID of the resource for which the rule got triggered triggered: type: integer required: true description: 0 if the specified incident got resolved/untriggered triggered_now: type: integer required: true description: | 0 in case this is not the first time the specified incident has raised an alert firing_since: type: string required: true description: | the time at which the rule raised an alert and sent a trigger to this API endpoint pending_since: type: string required: true description: | the time at which the rule evaluated to True and entered pending state. A rule can remain in pending state if a TriggerOffset has been configured. Datetime needed resolved_since: type: string required: true description: > the time at which the incident with the specified UUID resolved.\ Datetime needed """ # Do not publicly expose this API endpoint? if config.CILIA_SECRET_KEY != request.headers.get('Cilia-Secret-Key'): raise UnauthorizedError() params = params_from_request(request) keys = ( 'value', 'incident', 'triggered', 'triggered_now', 'firing_since', 'pending_since', 'resolved_since', ) for key in keys: if key not in params: raise RequiredParameterMissingError(key) # Get the rule's UUID. # TODO rule_id = request.matchdict['rule'] rule_id = params['rule_id'] # Get resource and incidents ids. incident_id = str(params['incident']) resource_id = str(params['resource']) # Get timestamps. firing_since = str(params['firing_since']) # pending_since = str(params['pending_since']) resolved_since = str(params['resolved_since']) try: value = params['value'] value = float(value) except (TypeError, ValueError) as err: log.error('Failed to cast "%s" to float: %r', value, err) raise BadRequestError('Failed to convert %s to float' % value) def int_to_bool(param): try: return bool(int(param or 0)) except (ValueError, TypeError) as err: log.error('Failed to cast int to bool: %r', err) raise BadRequestError('Failed to convert %s to boolean' % param) # Get flags indicating whether the incident has been (just) triggered. triggered = int_to_bool(params['triggered']) triggered_now = int_to_bool(params['triggered_now']) # Get the timestamp at which the rule's state changed. try: timestamp = resolved_since or firing_since timestamp = int(get_datetime(timestamp).strftime('%s')) except ValueError as err: log.error('Failed to cast datetime obj to unix timestamp: %r', err) raise BadRequestError(err) try: rule = Rule.objects.get(id=rule_id) except Rule.DoesNotExist: raise RuleNotFoundError() # Validate resource, if the rule is resource-bound. if not rule.is_arbitrary(): resource_type = rule.resource_model_name Model = get_resource_model(resource_type) try: resource = Model.objects.get(id=resource_id, owner=rule.owner_id) except Model.DoesNotExist: raise NotFoundError('%s %s' % (resource_type, resource_id)) if is_resource_missing(resource): raise NotFoundError('%s %s' % (resource_type, resource_id)) else: resource_type = resource_id = None # Record the trigger, if it's a no-data, to refer to it later. if isinstance(rule, NoDataRule): if triggered: NoDataRuleTracker.add(rule.id, resource.id) else: NoDataRuleTracker.remove(rule.id, resource.id) # Run chain of rule's actions. run_chained_actions( rule.id, incident_id, resource_id, resource_type, value, triggered, triggered_now, timestamp, ) return Response('OK', 200)