class MocProjects(base.MocBaseApi): """ API Endpoint for applying, or listing accessible projects. """ url = r'^moc/Projects/?$' task_type = 'moc_create_project' config_group = config_groups.DynamicNameConfigGroup( children=[ config_fields.BoolConfig( 'create_default_network', help_text='If set to faulse, overrides what is in setup_network.', default=True, sample_default=True, ), config_fields.StrConfig( 'region', help_text='Region for creating default network and quota.', default='RegionOne', sample_default='RegionOne' ), config_fields.StrConfig( "project_domain_id", help_text="Domain id for projects.", default="default", sample_default="Default" ), ] ) @utils.authenticated def post(self, request, format=None): request.data['email'] = request.keystone_user['username'] request.data['region'] = self.config['region'] request.data['domain_id'] = self.config['project_domain_id'] if not self.config['create_default_network']: request.data['setup_network'] = False if 'project_name' not in request.data: message = 'Missing project_name in request.' self.logger.info(message) return self.response(message, 400) project = self.identity.find_project( request.data['project_name'], self.config.project_domain_id) if project: message = ('Project %s already exists.' % request.data['project_name']) self.logger.info(message) return self.response_error(message, 409) return self.create_task(request) @utils.authenticated def get(self, request, format=None): # List other projects that a user has access to, or has applied for. pass
class CreateProjectAndUser(BaseDelegateAPI): url = r"^actions/CreateProjectAndUser/?$" config_group = groups.DynamicNameConfigGroup( children=[ fields.StrConfig( "default_region", help_text="Default region in which any potential resources may be created.", required=True, default="RegionOne", ), fields.StrConfig( "default_domain_id", help_text="Domain in which project and users will be created.", default="default", required=True, ), fields.StrConfig( "default_parent_id", help_text="Parent id under which this project will be created. " "Default is None, and will create under default domain.", default=None, ), ] ) task_type = "create_project_and_user" def post(self, request, format=None): """ Unauthenticated endpoint bound primarily to NewProjectWithUser. This process requires approval, so this will validate incoming data and create a task to be approved later. """ self.logger.info("(%s) - Starting new project task." % timezone.now()) class_conf = self.config # we need to set the region the resources will be created in: request.data["region"] = class_conf.default_region # domain request.data["domain_id"] = class_conf.default_domain_id # parent_id for new project, if null defaults to domain: request.data["parent_id"] = class_conf.default_parent_id self.task_manager.create_from_request(self.task_type, request) return Response({"notes": ["task created"]}, status=202)
class MocInviteUserAction(base.MocBaseAction): required = [ 'email', 'project_id', 'roles', ] serializer = serializers.MocInviteUserSerializer config_group = config_groups.DynamicNameConfigGroup(children=[ config_fields.StrConfig("user_domain_id", help_text="Domain to create projects in.", default="default", sample_default="Default") ]) def _get_email(self): """This is the email where the invitation will be sent.""" return self.email def _prepare(self): if not self._validate(): self.add_note('Validation failed at _prepare') return self.action.auto_approve = True self.action.state = "pending" self.action.need_token = True self.set_token_fields(["confirm", "user"]) def validate_token(self, token_data): if not self.valid or not token_data.get('confirm'): self.add_note('Invitation not valid or not accepted.') return False return True def write_to_approve_journal(self): pass def write_to_submit_journal(self, token_data): project = self.leader_identity.get_project(self.project_id) project_ref = {'name': project.name, 'domain_id': project.domain_id} services = self.find_services_for_project(project) self.submit_journal.append( operations.AddUserToProjectOperation(services, token_data['user'], project_ref, self.roles))
class MocHealthCheck(base.MocBaseApi): """ API Endpoint for checking the health of Adjutant. """ url = r'^moc/HealthCheck/?$' config_group = config_groups.DynamicNameConfigGroup( children=[ config_fields.BoolConfig( 'check_mailing_list', help_text='Check SSH connection to mailing list server.', default=True, sample_default=True, ), ] ) def get(self, request, format=None): errors = { 'MailingList': self._check_mailing_list() } return Response({'errors': errors}) def _get_action_defaults(self, action): return CONF.workflow.action_defaults.get(action) def _check_mailing_list(self): config = self._get_action_defaults('MailingListSubscribeAction') try: with mailing_list.Mailman(config.host, config.port, config.user, config.private_key) as mailman: mailman.is_already_subscribed('*****@*****.**', config.list) return [] except Exception as e: return [str(e)]
class NewDefaultNetworkAction(BaseAction, ProjectMixin): """ This action will setup all required basic networking resources so that a new user can launch instances right away. """ required = [ "setup_network", "project_id", "region", ] serializer = serializers.NewDefaultNetworkSerializer config_group = groups.DynamicNameConfigGroup( children=[ groups.ConfigGroup( "region_defaults", children=[ fields.StrConfig( "network_name", help_text="Name to be given to the default network.", default="default_network", ), fields.StrConfig( "subnet_name", help_text="Name to be given to the default subnet.", default="default_subnet", ), fields.StrConfig( "router_name", help_text="Name to be given to the default router.", default="default_router", ), fields.StrConfig( "public_network", help_text="ID of the public network.", ), fields.StrConfig( "subnet_cidr", help_text="CIDR for the default subnet.", ), fields.ListConfig( "dns_nameservers", help_text="DNS nameservers for the subnet.", ), ], ), fields.DictConfig( "regions", help_text="Specific per region config for default network. " "See 'region_defaults'.", default={}, ), ] ) def __init__(self, *args, **kwargs): super(NewDefaultNetworkAction, self).__init__(*args, **kwargs) def _validate_region(self): if not self.region: self.add_note("ERROR: No region given.") return False id_manager = user_store.IdentityManager() region = id_manager.get_region(self.region) if not region: self.add_note("ERROR: Region does not exist.") return False self.add_note("Region: %s exists." % self.region) return True def _validate(self): self.action.valid = validate_steps( [ self._validate_region, self._validate_project_id, self._validate_keystone_user_project_id, ] ) self.action.save() def _create_network(self): neutron = openstack_clients.get_neutronclient(region=self.region) try: region_config = self.config.regions[self.region] network_config = self.config.region_defaults.overlay(region_config) except KeyError: network_config = self.config.region_defaults if not self.get_cache("network_id"): try: network_body = { "network": { "name": network_config.network_name, "tenant_id": self.project_id, "admin_state_up": True, } } network = neutron.create_network(body=network_body) except Exception as e: self.add_note( "Error: '%s' while creating network: %s" % (e, network_config.network_name) ) raise self.set_cache("network_id", network["network"]["id"]) self.add_note( "Network %s created for project %s" % (network_config.network_name, self.project_id) ) else: self.add_note( "Network %s already created for project %s" % (network_config.network_name, self.project_id) ) if not self.get_cache("subnet_id"): try: subnet_body = { "subnet": { "network_id": self.get_cache("network_id"), "ip_version": 4, "tenant_id": self.project_id, "dns_nameservers": network_config.dns_nameservers, "cidr": network_config.subnet_cidr, } } subnet = neutron.create_subnet(body=subnet_body) except Exception as e: self.add_note("Error: '%s' while creating subnet" % e) raise self.set_cache("subnet_id", subnet["subnet"]["id"]) self.add_note("Subnet created for network %s" % network_config.network_name) else: self.add_note( "Subnet already created for network %s" % network_config.network_name ) if not self.get_cache("router_id"): try: router_body = { "router": { "name": network_config.router_name, "external_gateway_info": { "network_id": network_config.public_network }, "tenant_id": self.project_id, "admin_state_up": True, } } router = neutron.create_router(body=router_body) except Exception as e: self.add_note( "Error: '%s' while creating router: %s" % (e, network_config.router_name) ) raise self.set_cache("router_id", router["router"]["id"]) self.add_note("Router created for project %s" % self.project_id) else: self.add_note("Router already created for project %s" % self.project_id) if not self.get_cache("port_id"): try: interface_body = {"subnet_id": self.get_cache("subnet_id")} interface = neutron.add_interface_router( self.get_cache("router_id"), body=interface_body ) except Exception as e: self.add_note("Error: '%s' while attaching interface" % e) raise self.set_cache("port_id", interface["port_id"]) self.add_note("Interface added to router for subnet") else: self.add_note("Interface added to router for project %s" % self.project_id) def _prepare(self): # Note: Do we need to get this from cache? it is a required setting # self.project_id = self.action.task.cache.get('project_id', None) self._validate() def _approve(self): self._validate() if self.setup_network and self.valid: self._create_network() def _submit(self, token_data, keystone_user=None): pass
class UpdateProjectQuotasAction(BaseAction, QuotaMixin): """ Updates quota for a project to a given size in a list of regions """ required = [ "size", "project_id", "regions", ] serializer = serializers.UpdateProjectQuotasSerializer config_group = groups.DynamicNameConfigGroup( children=[ fields.FloatConfig( "size_difference_threshold", help_text="Precentage different allowed when matching quota sizes.", default=0.1, min=0, max=1, ), fields.IntConfig( "days_between_autoapprove", help_text="The allowed number of days between auto approved quota changes.", default=30, ), ] ) def _get_email(self): if CONF.identity.username_is_email: return self.action.task.keystone_user["username"] else: id_manager = user_store.IdentityManager() user = id_manager.users.get(self.keystone_user["user_id"]) email = user.email if email: return email self.add_note("User email address not set.") return None def _validate_quota_size_exists(self): size_list = CONF.quota.sizes.keys() if self.size not in size_list: self.add_note("Quota size: %s does not exist" % self.size) return False return True def _set_region_quota(self, region_name, quota_size): # Set the quota for an individual region quota_config = CONF.quota.sizes.get(quota_size, {}) if not quota_config: self.add_note( "Project quota not defined for size '%s' in region %s." % (quota_size, region_name) ) return quota_manager = QuotaManager( self.project_id, self.config.size_difference_threshold ) quota_manager.set_region_quota(region_name, quota_config) self.add_note( "Project quota for region %s set to %s" % (region_name, quota_size) ) def _can_auto_approve(self): wait_days = self.config.days_between_autoapprove task_list = models.Task.objects.filter( completed_on__gte=timezone.now() - timedelta(days=wait_days), task_type__exact=self.action.task.task_type, cancelled__exact=False, project_id__exact=self.project_id, ) changed_in_period = False # Check to see if there have been any updates in the relavent regions # recently for task in task_list: for action in task.actions: intersect = set(action.action_data["regions"]).intersection( self.regions ) if intersect: changed_in_period = True region_sizes = [] quota_manager = QuotaManager( self.project_id, self.config.size_difference_threshold ) for region in self.regions: current_size = quota_manager.get_region_quota_data( region, include_usage=False )["current_quota_size"] region_sizes.append(current_size) self.add_note( "Project has size '%s' in region: '%s'" % (current_size, region) ) # Check for preapproved_quotas preapproved_quotas = [] smaller_quotas = [] # If all region sizes are the same if region_sizes.count(region_sizes[0]) == len(region_sizes): preapproved_quotas = quota_manager.get_quota_change_options(region_sizes[0]) smaller_quotas = quota_manager.get_smaller_quota_options(region_sizes[0]) if self.size in smaller_quotas: self.add_note( "Quota size '%s' is in list of smaller quotas: %s" % (self.size, smaller_quotas) ) return True if changed_in_period: self.add_note( "Quota has already been updated within the auto " "approve time limit." ) return False if self.size not in preapproved_quotas: self.add_note( "Quota size '%s' not in preapproved list: %s" % (self.size, preapproved_quotas) ) return False self.add_note( "Quota size '%s' in preapproved list: %s" % (self.size, preapproved_quotas) ) return True def _validate(self): # Make sure the project id is valid and can be used self.action.valid = validate_steps( [ self._validate_project_id, self._validate_quota_size_exists, self._validate_regions_exist, self._validate_usage_lower_than_quota, ] ) self.action.save() def _prepare(self): self._validate() # Set auto-approval self.set_auto_approve(self._can_auto_approve()) def _approve(self): self._validate() if not self.valid or self.action.state == "completed": return # Use manager here instead, it will make it easier to add has_more # in later for region in self.regions: self._set_region_quota(region, self.size) self.action.state = "completed" self.action.task.cache["project_id"] = self.project_id self.action.task.cache["size"] = self.size self.action.save() def _submit(self, token_data, keystone_user=None): """ Nothing to do here. Everything is done at approve. """ pass
def make_task_config(task_class): config_group = groups.DynamicNameConfigGroup() config_group.register_child_config( fields.BoolConfig( "allow_auto_approve", help_text="Override if this task allows auto_approval. " "Otherwise uses task default.", default=task_class.allow_auto_approve, )) config_group.register_child_config( fields.ListConfig( "additional_actions", help_text="Additional actions to be run as part of the task " "after default actions.", default=task_class.additional_actions or [], )) config_group.register_child_config( fields.IntConfig( "token_expiry", help_text="Override for the task token expiry. " "Otherwise uses task default.", default=task_class.token_expiry, )) config_group.register_child_config( fields.DictConfig( "actions", help_text="Action config overrides over the action defaults. " "See 'adjutant.workflow.action_defaults'.", is_json=True, default=task_class.action_config or {}, sample_default={ "SomeCustomAction": { "some_action_setting": "<a-uuid-probably>" } }, )) config_group.register_child_config( fields.DictConfig( "emails", help_text="Email config overrides for this task over task defaults." "See 'adjutant.workflow.emails'.", is_json=True, default=task_class.email_config or {}, sample_default={ "initial": None, "token": { "subject": "Some custom subject", }, }, )) config_group.register_child_config( fields.DictConfig( "notifications", help_text= "Notification config overrides for this task over task defaults." "See 'adjutant.workflow.notifications'.", is_json=True, default=task_class.notification_config or {}, sample_default={ "standard_handlers": ["EmailNotification"], "error_handlers": ["EmailNotification"], "standard_handler_config": { "EmailNotification": { "emails": ["*****@*****.**"], "reply": "*****@*****.**", } }, "error_handler_config": { "EmailNotification": { "emails": ["*****@*****.**"], "reply": "*****@*****.**", } }, }, )) return config_group
class MailingListSubscribeAction(base.MocBaseAction): required = ['email'] serializer = serializers.MailingListSubscribeSerializer config_group = conf_group.DynamicNameConfigGroup(children=[ conf_field.StrConfig( "private_key", help_text="Location of private key for mailing list server.", default="/.ssh/id_rsa", sample_default="/.ssh/id_rsa", ), conf_field.HostNameConfig( "host", help_text="Mailing list server host.", default="mail.massopen.cloud", sample_default="mail.massopen.cloud", ), conf_field.IntConfig( "port", help_text="Mailing list server SSH port", default=22, sample_default=22, ), conf_field.StrConfig( "user", help_text="Mailing list server user.", default="moc-tools", sample_default="moc-tools", ), conf_field.StrConfig( "list", help_text="Mailing list to add users to.", default="kaizen-users", sample_default="kaizen-users", ), ], ) def _get_email(self): if CONF.identity.username_is_email: return self.action.task.keystone_user['username'] def _approve(self): try: with Mailman(self.config.host, self.config.port, self.config.user, self.config.private_key) as mailman: if mailman.is_already_subscribed(self._get_email(), self.config.list): self.add_note('%s already subscribed to mailing list.' % self._get_email()) else: mailman.subscribe(self._get_email(), self.config.list) self.add_note( '%s successfully subscribed to mailing list.' % self._get_email()) except paramiko.ssh_exception.SSHException as e: self.add_note('Unable to connect to Mailing List server. ' 'Proceeding regardless. %s' % str(e)) self.action.state = 'complete' self.action.save() def _submit(self, token_data): pass
class EmailNotification(base.BaseNotificationHandler): """ Basic email notification handler. Will send an email with the given templates. """ config_group = groups.DynamicNameConfigGroup(children=[ fields.ListConfig( "emails", help_text="List of email addresses to send this notification to.", item_type=types.String(regex=constants.EMAIL_REGEX), default=[], ), fields.StrConfig( "from", help_text="From email for this notification.", regex=constants.EMAIL_WITH_TEMPLATE_REGEX, sample_default="bounce+%(task_uuid)[email protected]", ), fields.StrConfig( "reply", help_text="Reply-to email for this notification.", regex=constants.EMAIL_REGEX, sample_default="*****@*****.**", ), fields.StrConfig( "template", help_text="Email template for this notification. " "No template will cause the email not to send.", default="notification.txt", ), fields.StrConfig( "html_template", help_text="Email html template for this notification.", ), ]) def _notify(self, task, notification): conf = self.config(task, notification) if not conf or not conf["emails"]: # Log that we did this!! note = ("Skipped sending notification for task: %s (%s) " "as notification handler conf is None, or no emails " "were configured." % (task.task_type, task.uuid)) self.logger.info("(%s) - %s" % (timezone.now(), note)) return template = loader.get_template(conf["template"], using="include_etc_templates") html_template = conf["html_template"] if html_template: html_template = loader.get_template(html_template, using="include_etc_templates") context = {"task": task, "notification": notification} if CONF.workflow.horizon_url: task_url = CONF.workflow.horizon_url notification_url = CONF.workflow.horizon_url if not task_url.endswith("/"): task_url += "/" if not notification_url.endswith("/"): notification_url += "/" task_url += "management/tasks/%s" % task.uuid notification_url += "management/notifications/%s" % notification.uuid context["task_url"] = task_url context["notification_url"] = notification_url if notification.error: subject = "Error - %s notification" % task.task_type else: subject = "%s notification" % task.task_type try: message = template.render(context) # from_email is the return-path and is distinct from the # message headers from_email = conf["from"] if not from_email: from_email = conf["reply"] elif "%(task_uuid)s" in from_email: from_email = from_email % {"task_uuid": task.uuid} # these are the message headers which will be visible to # the email client. headers = { "X-Adjutant-Task-UUID": task.uuid, # From needs to be set to be disctinct from return-path "From": conf["reply"], "Reply-To": conf["reply"], } email = EmailMultiAlternatives(subject, message, from_email, conf["emails"], headers=headers) if html_template: email.attach_alternative(html_template.render(context), "text/html") email.send(fail_silently=False) notification.acknowledged = True notification.save() except SMTPException as e: notes = { "errors": [("Error: '%s' while sending email notification") % e] } error_notification = Notification.objects.create( task=notification.task, notes=notes, error=True) error_notification.save()
class SendAdditionalEmailAction(BaseAction): serializer = serializers.SendAdditionalEmailSerializer config_group = groups.DynamicNameConfigGroup(children=[ _build_default_email_group("prepare"), _build_default_email_group("approve"), _build_default_email_group("submit"), ], ) def set_email(self, conf): self.emails = set() if conf.get("email_current_user"): self.add_note("Adding the current user's email address") if CONF.identity.username_is_email: self.emails.add(self.action.task.keystone_user["username"]) else: try: id_manager = user_store.IdentityManager() email = id_manager.get_user( self.action.task.keystone_user["user_id"]).email self.emails.add(email) except AttributeError: self.add_note("Could not add current user email address") if conf.get("email_roles"): roles = set(conf.get("email_roles")) project_id = self.action.task.keystone_user["project_id"] self.add_note("Adding email addresses for roles %s in project %s" % (roles, project_id)) id_manager = user_store.IdentityManager() users = id_manager.list_users(project_id) for user in users: user_roles = [role.name for role in user.roles] if roles.intersection(user_roles): if CONF.identity.username_is_email: self.emails.add(user.name) else: self.emails.add(user.email) if conf.get("email_task_cache"): task_emails = self.action.task.cache.get("additional_emails", []) if isinstance(task_emails, six.string_types): task_emails = [task_emails] for email in task_emails: self.emails.add(email) for email in conf.get("email_additional_addresses"): self.emails.add(email) def _validate(self): self.action.valid = True self.action.save() def _prepare(self): self.perform_action("prepare") def _approve(self): self.perform_action("approve") def _submit(self, token_data, keystone_user=None): self.perform_action("submit") def perform_action(self, stage): self._validate() task = self.action.task for action in task.actions: if not action.valid: return email_conf = self.config.get(stage) # If either of these are false we won't be sending anything. if not email_conf or not email_conf.get("template"): return self.set_email(email_conf) if not self.emails: self.add_note(self.emails) self.add_note("Email address not set. Stage: %s" % stage) return self.add_note("Sending emails to: %s" % self.emails) actions = {} for action in task.actions: act = action.get_action() actions[str(act)] = act context = {"task": task, "actions": actions} result = send_email(self.emails, context, email_conf, task) if not result: self.add_note("Unable to send additional email. Stage: %s" % stage) else: self.add_note("Additional email sent. Stage: %s" % stage)
class UserList(tasks.InviteUser): url = r"^openstack/users/?$" config_group = groups.DynamicNameConfigGroup( children=[ fields.ListConfig( "blacklisted_roles", help_text="Users with any of these roles will be hidden from the user list.", default=[], sample_default=["admin"], ), ] ) @utils.mod_or_admin def get(self, request): """Get a list of all users who have been added to a project""" class_conf = self.config blacklisted_roles = class_conf.blacklisted_roles user_list = [] id_manager = user_store.IdentityManager() project_id = request.keystone_user["project_id"] project = id_manager.get_project(project_id) can_manage_roles = id_manager.get_manageable_roles( request.keystone_user["roles"] ) active_emails = set() for user in id_manager.list_users(project): skip = False roles = [] for role in user.roles: if role.name in blacklisted_roles: skip = True continue roles.append(role.name) if skip: continue inherited_roles = [] for role in user.inherited_roles: if role.name in blacklisted_roles: skip = True continue inherited_roles.append(role.name) if skip: continue email = getattr(user, "email", "") enabled = user.enabled user_status = "Active" if enabled else "Account Disabled" active_emails.add(email) user_list.append( { "id": user.id, "name": user.name, "email": email, "roles": roles, "inherited_roles": inherited_roles, "cohort": "Member", "status": user_status, "manageable": set(can_manage_roles).issuperset(roles), } ) for user in id_manager.list_inherited_users(project): skip = False roles = [] for role in user.roles: if role.name in blacklisted_roles: skip = True continue roles.append(role.name) if skip: continue email = getattr(user, "email", "") enabled = user.enabled user_status = "Active" if enabled else "Account Disabled" user_list.append( { "id": user.id, "name": user.name, "email": email, "roles": roles, "inherited_roles": [], "cohort": "Inherited", "status": user_status, "manageable": False, } ) # Get my active tasks for this project: project_tasks = models.Task.objects.filter( project_id=project_id, task_type="invite_user_to_project", completed=0, cancelled=0, ) registrations = [] for task in project_tasks: status = "Invited" for token in task.tokens: if token.expired: status = "Expired" for notification in task.notifications: if notification.error: status = "Failed" for action in task.actions: if not action.valid: status = "Invalid" task_data = {} for action in task.actions: task_data.update(action.action_data) registrations.append( {"uuid": task.uuid, "task_data": task_data, "status": status} ) for task in registrations: # NOTE(adriant): commenting out for now as it causes more confusion # than it helps. May uncomment once different duplication checking # measures are in place. # if task['task_data']['email'] not in active_emails: user = { "id": task["uuid"], "name": task["task_data"]["email"], "email": task["task_data"]["email"], "roles": task["task_data"]["roles"], "inherited_roles": task["task_data"]["inherited_roles"], "cohort": "Invited", "status": task["status"], } if not CONF.identity.username_is_email: user["name"] = task["task_data"]["username"] user_list.append(user) return Response({"users": user_list})
class UserRoles(BaseDelegateAPI): url = r"^openstack/users/(?P<user_id>\w+)/roles/?$" config_group = groups.DynamicNameConfigGroup( children=[ fields.ListConfig( "blacklisted_roles", help_text="User with these roles will return not found.", default=[], sample_default=["admin"], ), ] ) task_type = "edit_user_roles" @utils.mod_or_admin def get(self, request, user_id): """ Get role info based on the user id. """ id_manager = user_store.IdentityManager() user = id_manager.get_user(user_id) no_user = {"errors": ["No user with this id."]} if not user: return Response(no_user, status=404) project_id = request.keystone_user["project_id"] project = id_manager.get_project(project_id) class_conf = self.config blacklisted_roles = class_conf.blacklisted_roles roles = [role.name for role in id_manager.get_roles(user, project)] roles_blacklisted = set(blacklisted_roles) & set(roles) inherited_roles = [ role.name for role in id_manager.get_roles(user, project, True) ] inherited_roles_blacklisted = set(blacklisted_roles) & set(inherited_roles) if not roles or roles_blacklisted or inherited_roles_blacklisted: return Response(no_user, status=404) return Response({"roles": roles, "inherited_roles": inherited_roles}) @utils.mod_or_admin def put(self, args, **kwargs): """ Add user roles to the current project. """ kwargs["remove_role"] = False return self._edit_user(args, **kwargs) @utils.mod_or_admin def delete(self, args, **kwargs): """Revoke user roles to the current project. This only supports Active users """ kwargs["remove_role"] = True return self._edit_user(args, **kwargs) def _edit_user(self, request, user_id, remove_role=False, format=None): """ Helper function to add or remove roles from a user """ request.data["remove"] = remove_role if "project_id" not in request.data: request.data["project_id"] = request.keystone_user["project_id"] request.data["user_id"] = user_id self.logger.info( "(%s) - New EditUser %s request." % (timezone.now(), request.method) ) self.task_manager.create_from_request(self.task_type, request) return Response({"notes": ["task created"]}, status=202)
class UserDetail(BaseDelegateAPI): url = r"^openstack/users/(?P<user_id>\w+)/?$" config_group = groups.DynamicNameConfigGroup( children=[ fields.ListConfig( "blacklisted_roles", help_text="User with these roles will return not found.", default=[], sample_default=["admin"], ), ] ) @utils.mod_or_admin def get(self, request, user_id): """ Get user info based on the user id. Will only find users in your project. """ id_manager = user_store.IdentityManager() user = id_manager.get_user(user_id) no_user = {"errors": ["No user with this id."]} if not user: return Response(no_user, status=404) class_conf = self.config blacklisted_roles = class_conf.blacklisted_roles project_id = request.keystone_user["project_id"] project = id_manager.get_project(project_id) roles = [role.name for role in id_manager.get_roles(user, project)] roles_blacklisted = set(blacklisted_roles) & set(roles) inherited_roles = [ role.name for role in id_manager.get_roles(user, project, True) ] inherited_roles_blacklisted = set(blacklisted_roles) & set(inherited_roles) if not roles or roles_blacklisted or inherited_roles_blacklisted: return Response(no_user, status=404) return Response( { "id": user.id, "username": user.name, "email": getattr(user, "email", ""), "roles": roles, "inherited_roles": inherited_roles, } ) @utils.mod_or_admin def delete(self, request, user_id): """ Remove this user from the project. This may cancel a pending user invite, or simply revoke roles. """ id_manager = user_store.IdentityManager() user = id_manager.get_user(user_id) project_id = request.keystone_user["project_id"] # NOTE(dale): For now, we only support cancelling pending invites. if user: return Response( { "errors": [ "Revoking keystone users not implemented. " "Try removing all roles instead." ] }, status=501, ) project_tasks = models.Task.objects.filter( project_id=project_id, task_type="invite_user_to_project", completed=0, cancelled=0, ) for task in project_tasks: if task.uuid == user_id: self.task_manager.cancel(task) return Response("Cancelled pending invite task!", status=200) return Response("Not found.", status=404)
class ResetUserPasswordAction(UserNameAction, UserMixin): """ Simple action to reset a password for a given user. """ required = ["domain_name", "username", "email"] serializer = serializers.ResetUserPasswordSerializer config_group = groups.DynamicNameConfigGroup( children=[ fields.ListConfig( "blacklisted_roles", help_text="Users with these roles cannot reset their passwords.", default=[], sample_default=["admin"], ), ], ) def __init__(self, *args, **kwargs): super(ResetUserPasswordAction, self).__init__(*args, **kwargs) def _validate_user_roles(self): id_manager = user_store.IdentityManager() roles = id_manager.get_all_roles(self.user) user_roles = [] for roles in roles.values(): user_roles.extend(role.name for role in roles) if set(self.config.blacklisted_roles) & set(user_roles): self.add_note("Cannot reset users with blacklisted roles.") return False return True def _validate_user_email(self): # NOTE(adriant): We only need to check the USERNAME_IS_EMAIL=False # case since '_validate_username_exists' will ensure the True case if not CONF.identity.username_is_email: if self.user and ( getattr(self.user, "email", None).lower() != self.email.lower() ): self.add_note("Existing user with non-matching email.") return False self.action.need_token = True self.set_token_fields(["password"]) self.add_note("Existing user with matching email.") return True def _validate(self): # Here, the order of validation matters # as each one adds new class variables self.action.valid = validate_steps( [ self._validate_domain_name, self._validate_username_exists, self._validate_user_roles, self._validate_user_email, ] ) self.action.save() def _prepare(self): self._validate() self.set_auto_approve() def _approve(self): self._validate() def _submit(self, token_data, keystone_user=None): self._validate() if not self.valid: return self.update_password(token_data["password"]) self.add_note("User %s password has been changed." % self.username)
class MocNewProjectAction(base.MocBaseAction): """Creates a new project for the current authenticated user.""" required = [ 'project_name', 'description', 'domain_id', # TODO(knikolla): It should be possible to fetch these from # SSO once we support OAuth 2.0 access tokens. 'organization', 'organization_role', 'services', 'phone', 'moc_contact', ] serializer = serializers.MocNewProjectSerializer config_group = config_groups.DynamicNameConfigGroup(children=[ config_fields.ListConfig( "default_roles", help_text="Roles to be given on project to the creating user.", default=["member", "project_admin"], sample_default=["member", "project_admin"]), config_fields.ListConfig( "enabled_services", help_text="Other services configured in the cloud.", default=['openshift'], ), ], ) def _validate_project_name(self): # Make sure project id doesn't exist. project = self.identity.find_project(self.project_name, self.domain_id) if project: return False return True def _validate_services(self): for service in self.services: if service not in self.config.enabled_services: return False return True def _get_email(self): return self.action.task.keystone_user['username'] def write_to_approve_journal(self): user_ref = self._get_user() project_ref = { 'name': self.project_name, 'domain_id': self.domain_id, 'description': self.description, 'organization': self.organization, 'owner': self._get_email(), } self.approve_journal.append( operations.CreateProjectOperation(self.services, project_ref)) self.approve_journal.append( operations.AddUserToProjectOperation(self.services, user_ref, project_ref, self.config.default_roles)) def write_to_submit_journal(self, token_data): pass