def transfer_ownership_to_user(request): """ Tags: ownership --- Transfer ownership of a resource If a resource isn't owned by the requesting user, then an UnauthorizedError error will be thrown, unless the requesting user is a member of the Owners team. """ auth_context = auth_context_from_request(request) params = params_from_request(request) if not params.get('user_id'): raise RequiredParameterMissingError('user_id') try: new_owner = User.objects.get(id=params['user_id']) except User.DoesNotExist: raise NotFoundError('User with id %s' % params['user_id']) for rtype, rids in params.get('resources', {}).iteritems(): Model = get_resource_model(rtype) for rid in rids: try: resource = Model.objects.get(owner=auth_context.owner, id=rid) resource.transfer_ownership(auth_context, new_owner) except Model.DoesNotExist: raise NotFoundError('%s with id %s' % (rtype, rid)) trigger_session_update(auth_context.owner) return Response('OK', 200)
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 delete_dns_zone(request): """ Delete a specific DNS zone under a cloud. --- """ auth_context = auth_context_from_request(request) cloud_id = request.matchdict['cloud'] zone_id = request.matchdict['zone'] # Do we need the cloud here, now that the models have been created? try: cloud = Cloud.objects.get(owner=auth_context.owner, id=cloud_id) except me.DoesNotExist: raise CloudNotFoundError try: zone = Zone.objects.get(owner=auth_context.owner, id=zone_id) except Zone.DoesNotExist: raise NotFoundError('Zone does not exist') auth_context.check_perm("zone", "remove", zone_id) zone.ctl.delete_zone() # Schedule a UI update trigger_session_update(auth_context.owner, ['zones']) return OK
def update_metric(user, metric_id, name=None, unit=None, cloud_id=None, machine_id=None): raise NotImplementedError() url = "%s/metrics/%s" % (config.CORE_URI, metric_id) headers = {'Authorization': get_auth_header(user)} params = { 'name': name, 'unit': unit, 'cloud_id': cloud_id, 'machine_id': machine_id, } try: resp = requests.put(url, headers=headers, params=params, verify=config.SSL_VERIFY) except requests.exceptions.SSLError as exc: raise SSLError() except Exception as exc: log.error("Exception updating metric: %r", exc) raise ServiceUnavailableError(exc=exc) if not resp.ok: log.error("Error updating metric %d:%s", resp.status_code, resp.text) raise BadRequestError(resp.text) trigger_session_update(user, [])
def delete_dns_record(request): """ Delete a specific DNS record under a zone. --- """ auth_context = auth_context_from_request(request) cloud_id = request.matchdict['cloud'] zone_id = request.matchdict['zone'] record_id = request.matchdict['record'] try: cloud = Cloud.objects.get(owner=auth_context.owner, id=cloud_id) except me.DoesNotExist: raise CloudNotFoundError try: zone = Zone.objects.get(owner=auth_context.owner, id=zone_id) except Zone.DoesNotExist: raise NotFoundError('Zone does not exist') try: record = Record.objects.get(zone=zone, id=record_id) except Record.DoesNotExist: raise NotFoundError('Record does not exist') auth_context.check_perm("record", "remove", record_id) record.ctl.delete_record() # Schedule a UI update trigger_session_update(auth_context.owner, ['zones']) return OK
def create_dns_zone(request): """ Create a new DNS zone under a specific cloud. --- """ auth_context = auth_context_from_request(request) cloud_id = request.matchdict['cloud'] auth_context.check_perm("cloud", "read", cloud_id) auth_context.check_perm("cloud", "create_resources", cloud_id) tags = auth_context.check_perm("zone", "add", None) # Try to get the specific cloud for which we will create the zone. try: cloud = Cloud.objects.get(owner=auth_context.owner, id=cloud_id) except me.DoesNotExist: raise CloudNotFoundError params = params_from_request(request) new_zone = Zone.add(owner=cloud.owner, cloud=cloud, **params).as_dict() if tags: resolve_id_and_set_tags(auth_context.owner, 'zone', new_zone['id'], tags, cloud_id=cloud_id) # Schedule a UI update trigger_session_update(auth_context.owner, ['zones']) return new_zone
def delete_schedule(request): """ Tags: schedules --- Deletes a schedule entry of a user. REMOVE permission required on schedule --- schedule_id: type: string required: true """ schedule_id = request.matchdict['schedule_id'] auth_context = auth_context_from_request(request) if not schedule_id: raise RequiredParameterMissingError('No schedule id provided') # Check if entry exists try: schedule = Schedule.objects.get(id=schedule_id, deleted=None) except me.DoesNotExist: raise ScheduleTaskNotFound() # SEC auth_context.check_perm('schedule', 'remove', schedule_id) # NOTE: Do not perform an atomic operation when marking a schedule as # deleted, since we do not wish to bypass pre-save validation/cleaning. schedule.deleted = datetime.utcnow() schedule.save() trigger_session_update(auth_context.owner, ['schedules']) return OK
def remove_tags_from_resource(owner, resource_obj, tags, *args, **kwargs): """ This function get a list of tags in the form [{'key': 'joe'}] or [{'key': 'joe', 'value': 'schmoe'}] and will delete them from the resource :param owner: the resource owner :param resource_obj: the resource object where the tags will be added :param rtype: resource type :param tags: list of tags to be deleted """ # ensure there are no duplicate tag keys because mongoengine will # raise exception for duplicates in query key_list = list(set(tags)) # create a query that will return all the tags with query = reduce(lambda q1, q2: q1.__or__(q2), map(lambda key: Q(key=key), key_list)) Tag.objects(Q(owner=owner) & Q(resource=resource_obj) & (query)).delete() # SEC owner.mapper.update(resource_obj) rtype = resource_obj._meta["collection"] trigger_session_update(owner, [rtype + 's' if not rtype.endswith('s') else rtype])
def delete_subnet(owner, subnet): """ Delete a subnet. """ subnet.ctl.delete() # Schedule a UI update trigger_session_update(owner, ['clouds'])
def rename_cloud(owner, cloud_id, new_name): """Renames cloud with given cloud_id.""" log.info("Renaming cloud: %s", cloud_id) cloud = Cloud.objects.get(owner=owner, id=cloud_id, deleted=None) cloud.ctl.rename(new_name) log.info("Succesfully renamed cloud '%s'", cloud_id) trigger_session_update(owner, ['clouds'])
def enable_monitoring(user, cloud_id, machine_id, name='', dns_name='', public_ips=None, no_ssh=False, dry=False, deploy_async=True, **kwargs): raise NotImplementedError() """Enable monitoring for a machine.""" cloud = Cloud.objects.get(owner=user, id=cloud_id, deleted=None) payload = { 'action': 'enable', 'no_ssh': True, 'dry': dry, 'name': name or cloud.title, 'public_ips': ",".join(public_ips or []), 'dns_name': dns_name, 'cloud_title': cloud.title, 'cloud_provider': cloud.provider, 'cloud_region': cloud.region, 'cloud_apikey': cloud.apikey, 'cloud_apisecret': cloud.apisecret, 'cloud_apiurl': cloud.apiurl, 'cloud_tenant_name': cloud.tenant_name, } url_scheme = "%s/clouds/%s/machines/%s/monitoring" try: resp = requests.post(url_scheme % (config.CORE_URI, cloud_id, machine_id), data=json.dumps(payload), headers={'Authorization': get_auth_header(user)}, verify=config.SSL_VERIFY) except requests.exceptions.SSLError as exc: log.error("%r", exc) raise SSLError() if not resp.ok: if resp.status_code == 402: raise PaymentRequiredError( resp.text.replace('Payment required: ', '')) else: raise ServiceUnavailableError() ret_dict = resp.json() if dry: return ret_dict if not no_ssh: deploy = mist.api.tasks.deploy_collectd if deploy_async: deploy = deploy.delay deploy(user.email, cloud_id, machine_id, ret_dict['extra_vars']) trigger_session_update(user, ['monitoring']) return ret_dict
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 delete_network(owner, network): """ Delete a network. All subnets attached to the network will be deleted before the network itself. """ network.ctl.delete() # Schedule a UI update trigger_session_update(owner, ['clouds'])
def disassociate(self, machine): """Disassociates a key from a machine.""" from mist.api.machines.models import KeyMachineAssociation log.info("Disassociating key of machine '%s' " % machine.machine_id) # removing key association KeyMachineAssociation.objects(key=self.key, machine=machine).delete() trigger_session_update(self.key.owner, ['keys'])
def associate_metric(machine, metric_id, name='', unit=''): """Associate a new metric to a machine.""" if not machine.monitoring.hasmonitoring: raise MethodNotAllowedError("Machine doesn't have monitoring enabled") metric = update_metric(machine.owner, metric_id, name, unit) if metric_id not in machine.monitoring.metrics: machine.monitoring.metrics.append(metric_id) machine.save() trigger_session_update(machine.owner, ['monitoring']) return metric
def create_dns_record(request): """ Tags: dns --- Creates a new record under a specific zone. CREATE_RESOURCES permission required on cloud. CREATE_RECORDS permission required on zone --- cloud: in: path required: true type: string zone: in: path required: true type: string """ auth_context = auth_context_from_request(request) cloud_id = request.matchdict['cloud'] try: cloud = Cloud.objects.get(owner=auth_context.owner, id=cloud_id) except me.DoesNotExist: raise CloudNotFoundError() zone_id = request.matchdict['zone'] try: zone = Zone.objects.get(owner=auth_context.owner, id=zone_id, cloud=cloud) except Zone.DoesNotExist: raise NotFoundError('Zone does not exist') auth_context.check_perm("cloud", "read", cloud_id) auth_context.check_perm("zone", "read", zone_id) auth_context.check_perm("zone", "create_records", zone_id) tags = auth_context.check_perm("record", "add", None) params = params_from_request(request) dns_cls = RECORDS[params['type']] rec = dns_cls.add(owner=auth_context.owner, zone=zone, **params) rec.assign_to(auth_context.user) if tags: resolve_id_and_set_tags(auth_context.owner, 'record', rec.id, tags, cloud_id=cloud_id, zone_id=zone_id) trigger_session_update(auth_context.owner, ['zones']) return rec.as_dict()
def rename(self, name): # replace io.methods.edit_key """Edit name of an existing key""" log.info("Renaming key '%s' to '%s'.", self.key.name, name) if self.key.name == name: log.warning("Same name provided. No reason to edit this key") return self.key.name = name self.key.save() log.info("Renamed key '%s' to '%s'.", self.key.name, name) trigger_session_update(self.key.owner, ['keys'])
def edit(self, name=None, description=None): """Edit name or description of an existing script""" log.info("Edit script '%s''.", self.script.name) if name: self.script.name = name if description: self.script.description = description self.script.save() log.info("Edit script: '%s'.", self.script.id) trigger_session_update(self.script.owner, ['scripts'])
def disassociate(self, machine): """Disassociates a key from a machine.""" log.info("Disassociating key of machine '%s' " % machine.machine_id) # removing key association key_assoc = machine.key_associations.filter(keypair=self.key) if key_assoc: machine.key_associations.remove(key_assoc[0]) machine.save() trigger_session_update(self.key.owner, ['keys'])
def delete(self): """Delete an existing Rule. This method deletes a rule, after verifying the requesting user's permissions. Attempting to delete a rule by directly invoking the Rule model's delete method will bypass RBAC. """ self.check_auth_context() self.rule.delete() trigger_session_update(self.rule.owner, ['monitoring'])
def set_default(self): from mist.api.keys.models import Key """Set a new key as default key, given a key_id""" log.info("Setting key with id '%s' as default.", self.key.id) Key.objects(owner=self.key.owner, default=True).update(default=False) self.key.default = True self.key.save() log.info("Successfully set key with id '%s' as default.", self.key.id) trigger_session_update(self.key.owner, ['keys'])
def add(self, fail_on_invalid_params=True, **kwargs): """Add an entry to the database This is only to be called by `Key.add` classmethod to create a key. Fields `owner` and `name` are already populated in `self.key`. The `self.key` is not yet saved. """ from mist.api.keys.models import Key rename_kwargs(kwargs, 'priv', 'private') # Check for invalid `kwargs` keys. errors = {} for key in kwargs: if key not in self.key._key_specific_fields: error = "Invalid parameter %s=%r." % (key, kwargs[key]) if fail_on_invalid_params: errors[key] = error else: log.warning(error) kwargs.pop(key) if errors: log.error("Error adding %s: %s", self.key, errors) raise BadRequestError({ 'msg': "Invalid parameters %s." % errors.keys(), 'errors': errors, }) for key, value in kwargs.iteritems(): setattr(self.key, key, value) if not Key.objects(owner=self.key.owner, default=True): self.key.default = True try: self.key.save() except me.ValidationError as exc: log.error("Error adding %s: %s", self.key.name, exc.to_dict()) raise BadRequestError({ 'msg': exc.message, 'errors': exc.to_dict() }) except me.NotUniqueError as exc: log.error("Key %s not unique error: %s", self.key.name, exc) raise KeyExistsError() # SEC self.key.owner.mapper.update(self.key) log.info("Added key with name '%s'", self.key.name) trigger_session_update(self.key.owner, ['keys'])
def delete_subnet(request): """ Tags: networks --- Delete a subnet READ permission required on cloud READ permission required on network READ permission required on subnet REMOVE permission required on subnet --- cloud_id: in: path required: true type: string network_id: in: path required: true type: string subnet_id: in: path required: true type: string """ cloud_id = request.matchdict['cloud'] subnet_id = request.matchdict['subnet'] network_id = request.matchdict['network'] auth_context = auth_context_from_request(request) # SEC auth_context.check_perm('cloud', 'read', cloud_id) auth_context.check_perm('network', 'read', network_id) auth_context.check_perm('network', 'edit_subnets', network_id) try: cloud = Cloud.objects.get(id=cloud_id, owner=auth_context.owner) except Cloud.DoesNotExist: raise CloudNotFoundError() try: network = Network.objects.get(id=network_id, cloud=cloud) except Network.DoesNotExist: raise NetworkNotFoundError() try: subnet = Subnet.objects.get(id=subnet_id, network=network) subnet.ctl.delete() except Subnet.DoesNotExist: raise SubnetNotFoundError() # Trigger a UI update. trigger_session_update(auth_context.owner, ['clouds']) return OK
def update_metric(owner, metric_id, name='', unit=''): """Update an existing metric.""" try: metric = Metric.objects.get(owner=owner, metric_id=metric_id) except Metric.DoesNotExist: metric = Metric(owner=owner, metric_id=metric_id) if name: metric.name = name if unit: metric.unit = unit metric.save() trigger_session_update(owner, ['monitoring']) return metric
def update_monitoring_options(owner, emails): """Set `emails` as global e-mail alert's recipients.""" from mist.api.helpers import is_email_valid # FIXME Send e-mails as a list, instead of string? emails = emails.replace(' ', '') emails = emails.replace('\n', ',') emails = emails.replace('\r', ',') owner.alerts_email = [ email for email in emails.split(',') if is_email_valid(email) ] owner.save() trigger_session_update(owner, ['monitoring']) return {'alerts_email': owner.alerts_email}
def disassociate_metric(machine, metric_id): """Disassociate a metric from a machine.""" if not machine.monitoring.hasmonitoring: raise MethodNotAllowedError("Machine doesn't have monitoring enabled") try: Metric.objects.get(owner=machine.owner, metric_id=metric_id) except Metric.DoesNotExist: raise NotFoundError("Invalid metric_id") if metric_id not in machine.monitoring.metrics: raise NotFoundError("Metric isn't associated with this Machine") machine.monitoring.metrics.remove(metric_id) machine.save() trigger_session_update(machine.owner, ['monitoring'])
def update_cloud(request): """ Tags: clouds --- Updates cloud with given cloud_id. EDIT permission required on cloud. Not all fields need to be specified, only the ones being modified --- cloud_id: in: path required: true type: string """ auth_context = auth_context_from_request(request) cloud_id = request.matchdict['cloud'] try: cloud = Cloud.objects.get(owner=auth_context.owner, id=cloud_id, deleted=None) except Cloud.DoesNotExist: raise NotFoundError('Cloud does not exist') params = params_from_request(request) creds = params if not creds: raise BadRequestError("You should provide your new cloud settings") auth_context.check_perm('cloud', 'edit', cloud_id) log.info("Updating cloud: %s", cloud_id) fail_on_error = params.pop('fail_on_error', True) fail_on_invalid_params = params.pop('fail_on_invalid_params', True) polling_interval = params.pop('polling_interval', None) # Edit the cloud cloud.ctl.update(fail_on_error=fail_on_error, fail_on_invalid_params=fail_on_invalid_params, **creds) try: polling_interval = int(polling_interval) except (ValueError, TypeError): pass else: cloud.ctl.set_polling_interval(polling_interval) log.info("Cloud with id '%s' updated successfully.", cloud.id) trigger_session_update(auth_context.owner, ['clouds']) return OK
def toggle_cloud(request): """ Tags: clouds --- Toggles cloud with given cloud id. EDIT permission required on cloud. --- cloud_id: in: path required: true type: string new_state: enum: - '0' - '1' required: true type: string """ auth_context = auth_context_from_request(request) cloud_id = request.matchdict['cloud'] try: cloud = Cloud.objects.get(owner=auth_context.owner, id=cloud_id, deleted=None) except Cloud.DoesNotExist: raise NotFoundError('Cloud does not exist') auth_context.check_perm('cloud', 'edit', cloud_id) new_state = params_from_request(request).get('new_state', None) dns_enabled = params_from_request(request).get('dns_enabled', None) if new_state == '1': cloud.ctl.enable() elif new_state == '0': cloud.ctl.disable() elif new_state: raise BadRequestError('Invalid cloud state') if dns_enabled == 1: cloud.ctl.dns_enable() elif dns_enabled == 0: cloud.ctl.dns_disable() elif dns_enabled: raise BadRequestError('Invalid DNS state') if new_state is None and dns_enabled is None: raise RequiredParameterMissingError('new_state or dns_enabled') trigger_session_update(auth_context.owner, ['clouds']) return OK
def add_cloud_v_2(owner, title, provider, params): """Add cloud to owner""" # FIXME: Some of these should be explicit arguments, others shouldn't exist fail_on_error = params.pop('fail_on_error', params.pop('remove_on_error', True)) monitoring = params.pop('monitoring', False) params.pop('title', None) params.pop('provider', None) # Find proper Cloud subclass. if not provider: raise RequiredParameterMissingError("provider") log.info("Adding new cloud in provider '%s'", provider) if provider not in cloud_models.CLOUDS: raise BadRequestError("Invalid provider '%s'." % provider) cloud_cls = cloud_models.CLOUDS[provider] # Class of Cloud model. # Add the cloud. cloud = cloud_cls.add(owner, title, fail_on_error=fail_on_error, fail_on_invalid_params=False, **params) ret = {'cloud_id': cloud.id} if provider == 'bare_metal' and monitoring: # Let's overload this a bit more by also combining monitoring. machine = Machine.objects.get(cloud=cloud) ret['monitoring'] = enable_monitoring( owner, cloud.id, machine.machine_id, no_ssh=not (machine.os_type == 'unix' and machine.key_associations)) # SEC owner.mapper.update(cloud) log.info("Cloud with id '%s' added succesfully.", cloud.id) trigger_session_update(owner, ['clouds']) c_count = Cloud.objects(owner=owner, deleted=None).count() if owner.clouds_count != c_count: owner.clouds_count = c_count owner.save() cloud.polling_interval = 1800 # 30 min * 60 sec/min cloud.save() ListMachinesPollingSchedule.add(cloud=cloud) return ret
def update_monitoring_options(owner, emails): """Set `emails` as global e-mail alert's recipients.""" from mist.api.helpers import is_email_valid # FIXME Send e-mails as a list, instead of string? emails = emails.replace(" ", "") emails = emails.replace("\n", ",") emails = emails.replace("\r", ",") owner.alerts_email = [ email for email in emails.split(",") if is_email_valid(email) ] owner.save() trigger_session_update(owner, ["monitoring"]) return {"alerts_email": owner.alerts_email}