示例#1
0
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)
示例#2
0
 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)
示例#3
0
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 {}
示例#4
0
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)
示例#5
0
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)]
示例#6
0
    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')
示例#7
0
 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)
示例#8
0
    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)
示例#9
0
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)
示例#10
0
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)
示例#11
0
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)
示例#12
0
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
示例#13
0
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 {}
示例#14
0
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)
示例#15
0
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)