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
config_group.register_child_config( fields.ListConfig( "active_delegate_apis", help_text="List of Active Delegate APIs.", required=True, default=[ "UserRoles", "UserDetail", "UserResetPassword", "UserList", "RoleList", ], # NOTE(adriant): for testing purposes we include ALL default APIs test_default=[ "UserRoles", "UserDetail", "UserResetPassword", "UserList", "RoleList", "SignUp", "UpdateProjectQuotas", "CreateProjectAndUser", "InviteUser", "ResetPassword", "EditUser", "UpdateEmail", ], )) delegate_apis_group = groups.ConfigGroup("delegate_apis", lazy_load=True)
group_name="completed", email_subject="Task Completed", email_reply="*****@*****.**", email_from="bounce+%(task_uuid)[email protected]", email_template="completed.txt", email_html_template=None, )) _notifications_defaults_group = groups.ConfigGroup("notifications") _task_defaults_group.register_child_config(_notifications_defaults_group) _notifications_defaults_group.register_child_config( fields.ListConfig( "standard_handlers", help_text="Handlers to use for standard notifications.", required=True, default=[ "EmailNotification", ], )) _notifications_defaults_group.register_child_config( fields.ListConfig( "error_handlers", help_text="Handlers to use for error notifications.", required=True, default=[ "EmailNotification", ], )) _notifications_defaults_group.register_child_config( fields.DictConfig( "standard_handler_config",
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
} config_group = groups.ConfigGroup("quota") config_group.register_child_config( fields.DictConfig( "sizes", help_text= "A definition of the quota size groups that Adjutant should use.", value_type=types.Dict(value_type=types.Dict()), check_value_type=True, is_json=True, default=DEFAULT_QUOTA_SIZES, )) config_group.register_child_config( fields.ListConfig( "sizes_ascending", help_text="An ascending list of all the quota size names, " "so that Adjutant knows their relative sizes/order.", default=["small", "medium", "large"], )) config_group.register_child_config( fields.DictConfig( "services", help_text= "A per region definition of what services Adjutant should manage " "quotas for. '*' means all or default region.", value_type=types.List(), default={"*": ["cinder", "neutron", "nova"]}, ))
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()
default="Do not ever use this awful secret in prod!!!!", secret=True, unsafe_default=True, )) config_group.register_child_config( fields.BoolConfig( "debug", help_text="Django debug mode is turned on.", default=False, unsafe_default=True, )) config_group.register_child_config( fields.ListConfig( "allowed_hosts", help_text="The Django allowed hosts", required=True, default=["*"], unsafe_default=True, )) config_group.register_child_config( fields.StrConfig( "secure_proxy_ssl_header", help_text="The header representing a HTTP header/value combination " "that signifies a request is secure.", default="HTTP_X_FORWARDED_PROTO", )) config_group.register_child_config( fields.StrConfig( "secure_proxy_ssl_header_value", help_text="The value representing a HTTP header/value combination " "that signifies a request is secure.",
def _build_default_email_group(group_name): email_group = groups.ConfigGroup(group_name) email_group.register_child_config( fields.StrConfig( "subject", help_text="Email subject for this stage.", default="Openstack Email Notification", )) email_group.register_child_config( fields.StrConfig( "from", help_text="From email for this stage.", regex=constants.EMAIL_WITH_TEMPLATE_REGEX, default="bounce+%(task_uuid)[email protected]", )) email_group.register_child_config( fields.StrConfig( "reply", help_text="Reply-to email for this stage.", regex=constants.EMAIL_WITH_TEMPLATE_REGEX, default="*****@*****.**", )) email_group.register_child_config( fields.StrConfig( "template", help_text="Email template for this stage. " "No template will cause the email not to send.", default=None, )) email_group.register_child_config( fields.StrConfig( "html_template", help_text="Email html template for this stage. " "No template will cause the email not to send.", default=None, )) email_group.register_child_config( fields.BoolConfig( "email_current_user", help_text="Email the user who started the task.", default=False, )) email_group.register_child_config( fields.BoolConfig( "email_task_cache", help_text="Send to an email set in the task cache.", default=False, )) email_group.register_child_config( fields.ListConfig( "email_roles", help_text="Send emails to the given roles on the project.", default=[], )) email_group.register_child_config( fields.ListConfig( "email_additional_addresses", help_text="Send emails to an arbitrary admin emails", item_type=types.String(regex=constants.EMAIL_WITH_TEMPLATE_REGEX), default=[], )) return email_group
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