def get_email_from_template(recipient_list, subject, template, settings, context): """Construct a message object from a template This creates the full MIME object that can be used to send an email with mixed HTML and text parts. FIXME(herb): we depend on the FE settings object right now. Since we're only getting called from the FE that's fine for now but we should clean this up. Args: recipient_list (list(str)): Email addresses that will receive this mail. subject (str): Subject of the email. template (str): Name of the template to use. settings (Settings): grouper.settings.Settings object grouper is run with context (dict(str: str)): Context for the template library. Returns: MIMEMultipart: Constructed object for the email message. """ template_env = get_template_env() sender = settings["from_addr"] context["url"] = settings["url"] text_template = template_env.get_template("email/{}_text.tmpl".format(template)).render( **context ) html_template = template_env.get_template("email/{}_html.tmpl".format(template)).render( **context ) text = MIMEText(text_template, "plain", "utf-8") html = MIMEText(html_template, "html", "utf-8") msg = MIMEMultipart("alternative") msg["Subject"] = subject msg["From"] = sender msg["To"] = ", ".join(recipient_list) msg.attach(text) msg.attach(html) if "references_header" in context: msg["References"] = msg["In-Reply-To"] = context["references_header"] return msg
def get_email_from_template(recipient_list, subject, template, settings, context): """Construct a message object from a template This creates the full MIME object that can be used to send an email with mixed HTML and text parts. FIXME(herb): we depend on the FE settings object right now. Since we're only getting called from the FE that's fine for now but we should clean this up. Args: recipient_list (list(str)): Email addresses that will receive this mail. subject (str): Subject of the email. template (str): Name of the template to use. settings (Settings): grouper.settings.Settings object grouper is run with context (dict(str: str)): Context for the template library. Returns: MIMEMultipart: Constructed object for the email message. """ template_env = get_template_env() sender = settings["from_addr"] context["url"] = settings["url"] text_template = template_env.get_template( "email/{}_text.tmpl".format(template) ).render(**context) html_template = template_env.get_template( "email/{}_html.tmpl".format(template) ).render(**context) text = MIMEText(text_template, "plain", "utf-8") html = MIMEText(html_template, "html", "utf-8") msg = MIMEMultipart("alternative") msg["Subject"] = subject msg["From"] = sender msg["To"] = ", ".join(recipient_list) msg.attach(text) msg.attach(html) if "references_header" in context: msg["References"] = msg["In-Reply-To"] = context["references_header"] return msg
def create_fe_application(settings, usecase_factory, deployment_name, xsrf_cookies=True, session=None): # type: (Settings, UseCaseFactory, str, bool, Callable[[], Session]) -> GrouperApplication tornado_settings = { "debug": settings.debug, "static_path": os.path.join(os.path.dirname(grouper.fe.__file__), "static"), "xsrf_cookies": xsrf_cookies, } handler_settings = { "session": session if session else Session, "template_env": get_template_env(deployment_name=deployment_name), "usecase_factory": usecase_factory, } handlers = [(route, handler_class, handler_settings) for (route, handler_class) in HANDLERS] return GrouperApplication(handlers, **tornado_settings)
def get_application(settings, sentry_client, deployment_name): # type: (Settings, SentryProxy, str) -> Application tornado_settings = { "static_path": os.path.join(os.path.dirname(grouper.fe.__file__), "static"), "debug": settings.debug, "xsrf_cookies": True, } my_settings = { "db_session": Session, "template_env": get_template_env(deployment_name=deployment_name), } application = Application(HANDLERS, my_settings=my_settings, sentry_client=sentry_client, **tornado_settings) return application
def update_request(session, request, user, new_status, comment): """Update a request. Args: session(sqlalchemy.orm.session.Session): database session request(models.PermissionRequest): request to update user(models.User): user making update new_status(models.base.constants.REQUEST_STATUS_CHOICES): new status comment(str): comment to include with status change Raises: grouper.audit.UserNotAuditor in case we're trying to add an audited permission to a group without auditors """ if request.status == new_status: # nothing to do return # make sure the grant can happen if new_status == "actioned": if request.permission.audited: # will raise UserNotAuditor if no auditors are owners of the group assert_controllers_are_auditors(request.group) # all rows we add have the same timestamp now = datetime.utcnow() # new status change row permission_status_change = PermissionRequestStatusChange( request=request, user_id=user.id, from_status=request.status, to_status=new_status, change_at=now, ).add(session) session.flush() # new comment Comment( obj_type=OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), obj_pk=permission_status_change.id, user_id=user.id, comment=comment, created_on=now, ).add(session) # update permissionRequest status request.status = new_status session.commit() if new_status == "actioned": # actually grant permission try: grant_permission(session, request.group.id, request.permission.id, request.argument) except IntegrityError: session.rollback() # audit log AuditLog.log( session, user.id, "update_perm_request", "updated permission request to status: {}".format(new_status), on_group_id=request.group_id, on_user_id=request.requester_id, on_permission_id=request.permission.id, ) session.commit() # send notification subj_template = 'email/pending_permission_request_subj.tmpl' subject = "Re: " + get_template_env().get_template(subj_template).render( permission=request.permission.name, group=request.group.name ) if new_status == "actioned": email_template = "permission_request_actioned" else: email_template = "permission_request_cancelled" email_context = { 'group_name': request.group.name, 'action_taken_by': user.name, 'reason': comment, 'permission_name': request.permission.name, 'argument': request.argument, } send_email(session, [request.requester.name], subject, email_template, settings, email_context)
def create_request(session, user, group, permission, argument, reason): """ Creates an permission request and sends notification to the responsible approvers. Args: session(sqlalchemy.orm.session.Session): database session user(models.User): user requesting permission group(models.Group): group requested permission would be applied to permission(models.Permission): permission in question to request argument(str): argument for the given permission reason(str): reason the permission should be granted Raises: RequestAlreadyExists if trying to create a request that is already pending NoOwnersAvailable if no owner is available for the requested perm + arg. """ # check if group already has perm + arg pair for _, existing_perm_name, _, existing_perm_argument, _ in group.my_permissions(): if permission.name == existing_perm_name and argument == existing_perm_argument: raise RequestAlreadyGranted() # check if request already pending for this perm + arg pair existing_count = session.query(PermissionRequest).filter( PermissionRequest.group_id == group.id, PermissionRequest.permission_id == permission.id, PermissionRequest.argument == argument, PermissionRequest.status == "pending", ).count() if existing_count > 0: raise RequestAlreadyExists() # determine owner(s) owners_by_arg_by_perm = get_owners_by_grantable_permission(session, separate_global=True) owner_arg_list = get_owner_arg_list( session, permission, argument, owners_by_arg_by_perm=owners_by_arg_by_perm, ) if not owner_arg_list: raise NoOwnersAvailable() pending_status = "pending" now = datetime.utcnow() # multiple steps to create the request request = PermissionRequest( requester_id=user.id, group_id=group.id, permission_id=permission.id, argument=argument, status=pending_status, requested_at=now, ).add(session) session.flush() request_status_change = PermissionRequestStatusChange( request=request, user=user, to_status=pending_status, change_at=now, ).add(session) session.flush() Comment( obj_type=OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), obj_pk=request_status_change.id, user_id=user.id, comment=reason, created_on=now, ).add(session) # send notification email_context = { "user_name": user.name, "group_name": group.name, "permission_name": permission.name, "argument": argument, "reason": reason, "request_id": request.id, "references_header": request.reference_id, } # TODO: would be nicer if it told you which group you're an approver of # that's causing this notification mail_to = [] global_owners = owners_by_arg_by_perm[GLOBAL_OWNERS]["*"] non_wildcard_owners = filter(lambda grant: grant[1] != '*', owner_arg_list) non_global_owners = filter(lambda grant: grant[0] not in global_owners, owner_arg_list) if any(non_wildcard_owners): # non-wildcard owners should get all the notifications mailto_owner_arg_list = non_wildcard_owners elif any(non_global_owners): mailto_owner_arg_list = non_global_owners else: # only the wildcards so they get the notifications mailto_owner_arg_list = owner_arg_list for owner, arg in mailto_owner_arg_list: mail_to += [u for t, u in owner.my_members() if t == 'User'] subj = get_template_env().get_template('email/pending_permission_request_subj.tmpl').render( permission=permission.name, group=group.name ) send_email(session, set(mail_to), subj, "pending_permission_request", settings, email_context) return request
def create_request(session, user, group, permission, argument, reason): """ Creates an permission request and sends notification to the responsible approvers. Args: session(sqlalchemy.orm.session.Session): database session user(models.User): user requesting permission group(models.Group): group requested permission would be applied to permission(models.Permission): permission in question to request argument(str): argument for the given permission reason(str): reason the permission should be granted Raises: RequestAlreadyExists if trying to create a request that is already pending NoOwnersAvailable if no owner is available for the requested perm + arg. grouper.audit.UserNotAuditor if the group has owners that are not auditors """ # check if group already has perm + arg pair for _, existing_perm_name, _, existing_perm_argument, _ in group.my_permissions(): if permission.name == existing_perm_name and argument == existing_perm_argument: raise RequestAlreadyGranted() # check if request already pending for this perm + arg pair existing_count = session.query(PermissionRequest).filter( PermissionRequest.group_id == group.id, PermissionRequest.permission_id == permission.id, PermissionRequest.argument == argument, PermissionRequest.status == "pending", ).count() if existing_count > 0: raise RequestAlreadyExists() # determine owner(s) owners_by_arg_by_perm = get_owners_by_grantable_permission(session, separate_global=True) owner_arg_list = get_owner_arg_list( session, permission, argument, owners_by_arg_by_perm=owners_by_arg_by_perm, ) if not owner_arg_list: raise NoOwnersAvailable() if permission.audited: # will raise UserNotAuditor if any owner of the group is not an auditor assert_controllers_are_auditors(group) pending_status = "pending" now = datetime.utcnow() # multiple steps to create the request request = PermissionRequest( requester_id=user.id, group_id=group.id, permission_id=permission.id, argument=argument, status=pending_status, requested_at=now, ).add(session) session.flush() request_status_change = PermissionRequestStatusChange( request=request, user=user, to_status=pending_status, change_at=now, ).add(session) session.flush() Comment( obj_type=OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), obj_pk=request_status_change.id, user_id=user.id, comment=reason, created_on=now, ).add(session) # send notification email_context = { "user_name": user.name, "group_name": group.name, "permission_name": permission.name, "argument": argument, "reason": reason, "request_id": request.id, "references_header": request.reference_id, } # TODO: would be nicer if it told you which group you're an approver of # that's causing this notification mail_to = [] global_owners = owners_by_arg_by_perm[GLOBAL_OWNERS]["*"] non_wildcard_owners = filter(lambda grant: grant[1] != '*', owner_arg_list) non_global_owners = filter(lambda grant: grant[0] not in global_owners, owner_arg_list) if any(non_wildcard_owners): # non-wildcard owners should get all the notifications mailto_owner_arg_list = non_wildcard_owners elif any(non_global_owners): mailto_owner_arg_list = non_global_owners else: # only the wildcards so they get the notifications mailto_owner_arg_list = owner_arg_list for owner, arg in mailto_owner_arg_list: mail_to += [u for t, u in owner.my_members() if t == 'User'] subj = get_template_env().get_template('email/pending_permission_request_subj.tmpl').render( permission=permission.name, group=group.name ) send_email(session, set(mail_to), subj, "pending_permission_request", settings, email_context) return request
def fe_app(session, standard_graph): my_settings = { "db_session": lambda: session, "template_env": get_template_env(), } return Application(FE_HANDLERS, my_settings=my_settings)