def edit_member(self, requester, user_or_group, reason, **kwargs): """ Edit an existing member (User or Group) of a group. This takes the same parameters as add_member, except that we do not allow you to set a status: this only works on existing members. Any option that is not passed is not updated, and instead, the existing value for this user is kept. """ logging.debug( "Editing member (%s) in %s", user_or_group.name, self.groupname ) persist_group_member_changes( session=self.session, group=self, requester=requester, member=user_or_group, status="actioned", reason=reason, **kwargs ) member_type = user_or_group.member_type message = "Edit member {} {}: {}".format( OBJ_TYPES_IDX[member_type].lower(), user_or_group.name, reason) AuditLog.log(self.session, requester.id, 'edit_member', message, on_group_id=self.id)
def post(self, user_id=None, name=None, key_id=None, tag_id=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() try: key = get_public_key(self.session, user.id, key_id) except KeyNotFound: return self.notfound() tag = PublicKeyTag.get(self.session, id=tag_id) if not tag: return self.notfound() try: remove_tag_from_public_key(self.session, key, tag) except TagNotOnKey: return self.redirect("/users/{}?refresh=yes".format(user.name)) AuditLog.log( self.session, self.current_user.id, "untag_public_key", "Untagged public key: {}".format(key.fingerprint_sha256), on_tag_id=tag.id, on_user_id=user.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def test_group_logdump(make_session, session, users, groups, tmpdir): make_session.return_value = session groupname = 'team-sre' group_id = groups[groupname].id yesterday = date.today() - timedelta(days=1) fn = tmpdir.join('out.csv').strpath call_main('group', 'log_dump', groupname, yesterday.isoformat(), '--outfile', fn) with open(fn, 'r') as fh: out = fh.read() assert not out, 'nothing yet' AuditLog.log(session, users['*****@*****.**'].id, 'make_noise', 'making some noise', on_group_id=group_id) session.commit() call_main('group', 'log_dump', groupname, yesterday.isoformat(), '--outfile', fn) with open(fn, 'r') as fh: entries = [x for x in csv.reader(fh)] assert len(entries) == 1, 'should capture our new audit log entry' log_time, actor, description, action, extra = entries[0] assert groupname in extra
def test_group_logdump(session, tmpdir, users, groups): # noqa: F811 groupname = "team-sre" group_id = groups[groupname].id yesterday = date.today() - timedelta(days=1) fn = tmpdir.join("out.csv").strpath call_main(session, tmpdir, "group", "log_dump", groupname, yesterday.isoformat(), "--outfile", fn) with open(fn, "r") as fh: out = fh.read() assert not out, "nothing yet" AuditLog.log(session, users["*****@*****.**"].id, "make_noise", "making some noise", on_group_id=group_id) session.commit() call_main(session, tmpdir, "group", "log_dump", groupname, yesterday.isoformat(), "--outfile", fn) with open(fn, "r") as fh: entries = [x for x in csv.reader(fh)] assert len(entries) == 1, "should capture our new audit log entry" log_time, actor, description, action, extra = entries[0] assert groupname in extra
def post(self, group_id=None, name=None, account_id=None, accountname=None, mapping_id=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() service_account = ServiceAccount.get(self.session, account_id, accountname) if not service_account: return self.notfound() if not self.check_access(self.session, self.current_user, service_account): return self.forbidden() mapping = ServiceAccountPermissionMap.get(self.session, mapping_id) if not mapping: return self.notfound() permission = mapping.permission argument = mapping.argument mapping.delete(self.session) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log(self.session, self.current_user.id, "revoke_permission", "Revoked permission with argument: {}".format(argument), on_permission_id=permission.id, on_group_id=group.id, on_user_id=service_account.user.id) return self.redirect("/groups/{}/service/{}?refresh=yes".format( group.name, service_account.user.username))
def post(self, user_id=None, name=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserShellForm(self.request.arguments) form.shell.choices = settings.shell if not form.validate(): return self.render( "user-shell.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) user.set_metadata(USER_METADATA_SHELL_KEY, form.data["shell"]) user.add(self.session) self.session.commit() AuditLog.log(self.session, self.current_user.id, 'changed_shell', 'Changed shell: {}'.format(form.data["shell"]), on_user_id=user.id) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, name=None, mapping_id=None): mapping = TagPermissionMap.get(self.session, id=mapping_id) if not mapping: return self.notfound() if not user_has_permission(self.session, self.current_user, TAG_EDIT, mapping.tag.name): return self.forbidden() permission = mapping.permission tag = mapping.tag mapping.delete(self.session) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log( self.session, self.current_user.id, "revoke_tag_permission", "Revoked permission with argument: {}".format(mapping.argument), on_tag_id=tag.id, on_permission_id=permission.id, ) return self.redirect("/tags/{}?refresh=yes".format(tag.name))
def post(self, user_id=None, name=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() try: if user.role_user: disable_role_user(self.session, user=user) else: disable_user(self.session, user) except PluginRejectedDisablingUser as e: alert = Alert("danger", str(e)) return self.redirect("/users/{}".format(user.name), alerts=[alert]) self.session.commit() AuditLog.log( self.session, self.current_user.id, "disable_user", "Disabled user.", on_user_id=user.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, user_id=None, name=None, key_id=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() try: key = get_public_key(self.session, user.id, key_id) delete_public_key(self.session, user.id, key_id) except KeyNotFound: return self.notfound() AuditLog.log(self.session, self.current_user.id, 'delete_public_key', 'Deleted public key: {}'.format(key.fingerprint), on_user_id=user.id) email_context = { "actioner": self.current_user.name, "changed_user": user.name, "action": "removed", } send_email(self.session, [user.name], 'Public SSH key removed', 'ssh_keys_changed', settings, email_context) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self): form = TagCreateForm(self.request.arguments) if not form.validate(): return self.render( "tag-create.html", form=form, alerts=self.get_form_alerts(form.errors) ) tag = PublicKeyTag(name=form.data["tagname"], description=form.data["description"]) try: tag.add(self.session) self.session.flush() except IntegrityError: self.session.rollback() form.tagname.errors.append("{} already exists".format(form.data["tagname"])) return self.render( "tag-create.html", form=form, alerts=self.get_form_alerts(form.errors) ) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log( self.session, self.current_user.id, "create_tag", "Created new tag.", on_tag_id=tag.id ) return self.redirect("/tags/{}?refresh=yes".format(tag.name))
def edit_member(self, requester, user_or_group, reason, **kwargs): """ Edit an existing member (User or Group) of a group. This takes the same parameters as add_member, except that we do not allow you to set a status: this only works on existing members. Any option that is not passed is not updated, and instead, the existing value for this user is kept. """ logging.debug("Editing member (%s) in %s", user_or_group.name, self.groupname) persist_group_member_changes(session=self.session, group=self, requester=requester, member=user_or_group, status="actioned", reason=reason, **kwargs) member_type = user_or_group.member_type message = "Edit member {} {}: {}".format( OBJ_TYPES_IDX[member_type].lower(), user_or_group.name, reason) AuditLog.log(self.session, requester.id, "edit_member", message, on_group_id=self.id)
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None user_id = kwargs.get("user_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserGitHubForm(self.request.arguments) if not form.validate(): return self.render( "user-github.html", form=form, user=user, alerts=self.get_form_alerts(form.errors) ) new_username = form.data["username"] if new_username == "": new_username = None set_user_metadata(self.session, user.id, USER_METADATA_GITHUB_USERNAME_KEY, new_username) AuditLog.log( self.session, self.current_user.id, "changed_github_username", "Changed GitHub username: {}".format(form.data["username"]), on_user_id=user.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") user = User.get(self.session, name=name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserShellForm(self.request.arguments) form.shell.choices = settings().shell if not form.validate(): return self.render("user-shell.html", form=form, user=user, alerts=self.get_form_alerts(form.errors)) set_user_metadata(self.session, user.id, USER_METADATA_SHELL_KEY, form.data["shell"]) AuditLog.log( self.session, self.current_user.id, "changed_shell", "Changed shell: {}".format(form.data["shell"]), on_user_id=user.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, name=None, mapping_id=None): grantable = self.current_user.my_grantable_permissions() if not grantable: return self.forbidden() mapping = PermissionMap.get(self.session, id=mapping_id) if not mapping: return self.notfound() allowed = False for perm in grantable: if perm[0].name == mapping.permission.name: if matches_glob(perm[1], mapping.argument): allowed = True if not allowed: return self.forbidden() permission = mapping.permission group = mapping.group mapping.delete(self.session) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log(self.session, self.current_user.id, 'revoke_permission', 'Revoked permission with argument: {}'.format(mapping.argument), on_group_id=group.id, on_permission_id=permission.id) return self.redirect('/groups/{}?refresh=yes'.format(group.name))
def disable_permission_auditing(session, permission_name, actor_user_id): """Set a permission as audited. Args: session(models.base.session.Session): database session permission_name(str): name of permission in question actor_user_id(int): id of user who is disabling auditing """ permission = get_permission(session, permission_name) if not permission: raise NoSuchPermission(name=permission_name) permission.audited = False AuditLog.log( session, actor_user_id, "disable_auditing", "Disabled auditing.", on_permission_id=permission.id, ) Counter.incr(session, "updates") session.commit()
def notify_nonauditor_promoted(settings, session, user, auditors_group, group_names): # type: (Settings, Session, User, Group, Set[str]) -> None """Send notification that a nonauditor has been promoted to be an auditor. Handles email notification and audit logging. Args: settings: Grouper Settings object for current run. session: Object for db session. user: The user that has been promoted. auditors_group: The auditors group group_names: The audited groups in which the user was previously a non-auditor approver. """ member_name = user.username recipients = [member_name] auditors_group_name = auditors_group.groupname audit_data = { "action": "nonauditor_promoted", "actor_id": user.id, "description": "Added {} to group {}".format(member_name, auditors_group_name), } AuditLog.log(session, on_user_id=user.id, on_group_id=auditors_group.id, **audit_data) email_context = {"auditors_group_name": auditors_group_name, "member_name": member_name} send_email( session=session, recipients=recipients, subject='Added as member to group "{}"'.format(auditors_group_name), template="nonauditor_promoted", settings=settings, context=email_context, )
def post(self, user_id=None, name=None, key_id=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() try: key = get_public_key(self.session, user.id, key_id) delete_public_key(self.session, user.id, key_id) except KeyNotFound: return self.notfound() AuditLog.log(self.session, self.current_user.id, 'delete_public_key', 'Deleted public key: {}'.format(key.fingerprint_sha256), on_user_id=user.id) email_context = { "actioner": self.current_user.name, "changed_user": user.name, "action": "removed", } send_email(self.session, [user.name], 'Public SSH key removed', 'ssh_keys_changed', settings, email_context) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") password_id = int(self.get_path_argument("password_id")) user = User.get(self.session, name=name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() password = UserPassword.get(self.session, user=user, id=password_id) try: delete_user_password(self.session, password.name, user.id) except PasswordDoesNotExist: # if the password doesn't exist, we can pretend like it did and that we deleted it return self.redirect("/users/{}?refresh=yes".format(user.username)) AuditLog.log( self.session, self.current_user.id, "delete_password", "Deleted password: {}".format(password.name), on_user_id=user.id, ) self.session.commit() return self.redirect("/users/{}?refresh=yes".format(user.username))
def post(self, user_id=None, name=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserEnableForm(self.request.arguments) if not form.validate(): # TODO: add error message return self.redirect("/users/{}?refresh=yes".format(user.name)) if user.role_user: enable_service_account(self.session, actor=self.current_user, preserve_membership=form.preserve_membership.data, user=user) else: enable_user(self.session, user, self.current_user, preserve_membership=form.preserve_membership.data) self.session.commit() AuditLog.log(self.session, self.current_user.id, 'enable_user', 'Enabled user.', on_user_id=user.id) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, name=None, mapping_id=None): mapping = PermissionMap.get(self.session, id=mapping_id) if not mapping: return self.notfound() if not self.check_access(self.session, mapping, self.current_user): return self.forbidden() permission = mapping.permission group = mapping.group mapping.delete(self.session) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log(self.session, self.current_user.id, 'revoke_permission', 'Revoked permission with argument: {}'.format( mapping.argument), on_group_id=group.id, on_permission_id=permission.id) return self.redirect('/groups/{}?refresh=yes'.format(group.name))
def test_group_logdump(session, tmpdir, users, groups): # noqa: F811 groupname = "team-sre" group_id = groups[groupname].id yesterday = date.today() - timedelta(days=1) fn = tmpdir.join("out.csv").strpath call_main( session, tmpdir, "group", "log_dump", groupname, yesterday.isoformat(), "--outfile", fn ) with open(fn, "r") as fh: out = fh.read() assert not out, "nothing yet" AuditLog.log( session, users["*****@*****.**"].id, "make_noise", "making some noise", on_group_id=group_id ) session.commit() call_main( session, tmpdir, "group", "log_dump", groupname, yesterday.isoformat(), "--outfile", fn ) with open(fn, "r") as fh: entries = [x for x in csv.reader(fh)] assert len(entries) == 1, "should capture our new audit log entry" log_time, actor, description, action, extra = entries[0] assert groupname in extra
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() if not self.current_user.can_manage(group): return self.forbidden() form = GroupRemoveForm(self.request.arguments) if not form.validate(): return self.send_error(status_code=400) member_type, member_name = form.data["member_type"], form.data["member"] members = group.my_members() if not members.get((member_type.capitalize(), member_name), None): return self.notfound() removed_member = get_user_or_group(self.session, member_name, user_or_group=member_type) if self.current_user == removed_member: return self.send_error( status_code=400, reason="Can't remove yourself. Leave group instead." ) group.revoke_member(self.current_user, removed_member, "Removed by owner/np-owner/manager") AuditLog.log(self.session, self.current_user.id, 'remove_from_group', '{} was removed from the group.'.format(removed_member.name), on_group_id=group.id, on_user_id=removed_member.id) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def create_service_account(session, actor, name, description, machine_set, owner): # type: (Session, User, str, str, str, Group) -> ServiceAccount """Creates a service account and its underlying user. Also adds the service account to the list of accounts managed by the owning group. Throws: BadMachineSet: if some plugin rejected the machine set DuplicateServiceAccount: if a user with the given name already exists """ user = User(username=name, is_service_account=True) service_account = ServiceAccount(user=user, description=description, machine_set=machine_set) if machine_set is not None: _check_machine_set(service_account, machine_set) try: user.add(session) service_account.add(session) session.flush() except IntegrityError: session.rollback() raise DuplicateServiceAccount("User {} already exists".format(name)) # Counter is updated here and the session is committed, so we don't need an additional update # or commit for the account creation. add_service_account(session, owner, service_account) AuditLog.log(session, actor.id, "create_service_account", "Created new service account.", on_group_id=owner.id, on_user_id=service_account.user_id) return service_account
def post(self, tag_id=None, name=None): tag = PublicKeyTag.get(self.session, tag_id, name) if not tag: return self.notfound() if not user_has_permission(self.session, self.current_user, TAG_EDIT, tag.name): return self.forbidden() form = TagEditForm(self.request.arguments, obj=tag) if not form.validate(): return self.render( "tag-edit.html", tag=tag, form=form, alerts=self.get_form_alerts(form.errors) ) tag.description = form.data["description"] tag.enabled = form.data["enabled"] Counter.incr(self.session, "updates") try: self.session.commit() except IntegrityError: self.session.rollback() form.tagname.errors.append( "{} already exists".format(form.data["tagname"]) ) return self.render( "tag-edit.html", tag=tag, form=form, alerts=self.get_form_alerts(form.errors) ) AuditLog.log(self.session, self.current_user.id, 'edit_tag', 'Edited tag.', on_tag_id=tag.id) return self.redirect("/tags/{}".format(tag.name))
def post(self, user_id=None, name=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserEnableForm(self.request.arguments) if not form.validate(): # TODO: add error message return self.redirect("/users/{}?refresh=yes".format(user.name)) if user.role_user: enable_service_account( self.session, actor=self.current_user, preserve_membership=form.preserve_membership.data, user=user) else: enable_user(self.session, user, self.current_user, preserve_membership=form.preserve_membership.data) self.session.commit() AuditLog.log(self.session, self.current_user.id, 'enable_user', 'Enabled user.', on_user_id=user.id) return self.redirect("/users/{}?refresh=yes".format(user.name))
def create_role_user(session, actor, name, description, canjoin): # type (Session, User, str, str, str) -> None """DEPRECATED: Do not use in production code Creates a service account with the given name, description, and canjoin status Args: session: the database session actor: the user creating the service account name: the name of the service account description: description of the service account canjoin: the canjoin status for management of the service account Throws: IntegrityError: if a user or group with the given name already exists """ user = User(username=name, role_user=True) group = Group(groupname=name, description=description, canjoin=canjoin) user.add(session) group.add(session) group.add_member(actor, actor, "Group Creator", "actioned", None, "np-owner") group.add_member(actor, user, "Service Account", "actioned", None, "member") session.commit() AuditLog.log( session, actor.id, "create_role_user", "Created new service account.", on_group_id=group.id, on_user_id=user.id, )
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") group = Group.get(self.session, name=name) if not group: return self.notfound() members = group.my_members() if not user_role(self.current_user, members) in ("owner", "np-owner"): return self.forbidden() # Enabling and disabling service accounts via the group endpoints is forbidden # because we need the preserve_membership data that is only available via the # UserEnable form. if is_role_user(self.session, group=group): return self.forbidden() group.enable() self.session.commit() AuditLog.log( self.session, self.current_user.id, "enable_group", "Enabled group.", on_group_id=group.id, ) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def post(self, group_id=None, name=None, account_id=None, accountname=None, mapping_id=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() service_account = ServiceAccount.get(self.session, account_id, accountname) if not service_account: return self.notfound() if not self.check_access(self.session, self.current_user, service_account): return self.forbidden() mapping = ServiceAccountPermissionMap.get(self.session, mapping_id) if not mapping: return self.notfound() permission = mapping.permission argument = mapping.argument mapping.delete(self.session) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log( self.session, self.current_user.id, "revoke_permission", "Revoked permission with argument: {}".format(argument), on_permission_id=permission.id, on_group_id=group.id, on_user_id=service_account.user.id, ) return self.redirect( "/groups/{}/service/{}?refresh=yes".format(group.name, service_account.user.username) )
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() members = group.my_members() if not user_role(self.current_user, members) in ("owner", "np-owner"): return self.forbidden() # Enabling and disabling service accounts via the group endpoints is forbidden # because we need the preserve_membership data that is only available via the # UserEnable form. if is_role_user(self.session, group=group): return self.forbidden() group.disable() self.session.commit() AuditLog.log(self.session, self.current_user.id, 'disable_group', 'Disabled group.', on_group_id=group.id) if group.audit: # complete the audit group.audit.complete = True self.session.commit() AuditLog.log(self.session, self.current_user.id, 'complete_audit', 'Disabling group completes group audit.', on_group_id=group.id) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def disable_permission_auditing(session: Session, permission_name: str, actor_user_id: int) -> None: """Set a permission as audited. Args: session: Database session permission_name: Name of permission in question actor_user_id: ID of user who is disabling auditing """ permission = get_permission(session, permission_name) if not permission: raise NoSuchPermission(name=permission_name) permission.audited = False AuditLog.log( session, actor_user_id, "disable_auditing", "Disabled auditing.", on_permission_id=permission.id, ) Counter.incr(session, "updates") session.commit()
def post(self, user_id=None, name=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserPasswordForm(self.request.arguments) if not form.validate(): return self.render("user-password-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors)) pass_name = form.data["name"] password = form.data["password"] try: add_new_user_password(self.session, pass_name, password, user.id) except PasswordAlreadyExists: self.session.rollback() form.name.errors.append("Name already in use.") return self.render("user-password-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors)) AuditLog.log( self.session, self.current_user.id, "add_password", "Added password: {}".format(pass_name), on_user_id=user.id, ) email_context = {"actioner": self.current_user.name, "changed_user": user.name, "pass_name": pass_name} send_email(self.session, [user.name], "User password created", "user_password_created", settings, email_context) return self.redirect("/users/{}?refresh=yes".format(user.name))
def disable_permission_auditing(session, permission_name, actor_user_id): """Set a permission as audited. Args: session(models.base.session.Session): database session permission_name(str): name of permission in question actor_user_id(int): id of user who is disabling auditing """ permission = get_permission(session, permission_name) if not permission: raise NoSuchPermission(name=permission_name) permission._audited = False AuditLog.log( session, actor_user_id, "disable_auditing", "Disabled auditing.", on_permission_id=permission.id, ) Counter.incr(session, "updates") session.commit()
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() if not user_can_manage_group(self.session, group, self.current_user): return self.forbidden() form = GroupRemoveForm(self.request.arguments) if not form.validate(): return self.send_error(status_code=400) member_type, member_name = form.data["member_type"], form.data[ "member"] members = group.my_members() if not members.get((member_type.capitalize(), member_name), None): return self.notfound() removed_member = get_user_or_group(self.session, member_name, user_or_group=member_type) if self.current_user == removed_member: return self.send_error( status_code=400, reason="Can't remove yourself. Leave group instead.") role_user = is_role_user(self.session, group=group) if (role_user and get_role_user(self.session, group=group).user.name == removed_member.name): return self.send_error( status_code=400, reason= "Can't remove a service account user from the service account group." ) try: group.revoke_member(self.current_user, removed_member, "Removed by owner/np-owner/manager") AuditLog.log(self.session, self.current_user.id, 'remove_from_group', '{} was removed from the group.'.format( removed_member.name), on_group_id=group.id, on_user_id=removed_member.id) except PluginRejectedGroupMembershipUpdate as e: alert = Alert("danger", str(e)) if role_user: return self.redirect("/service/{}".format(group.name), alerts=[alert]) else: return self.redirect("/groups/{}".format(group.name), alerts=[alert]) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() members = group.my_members() if not user_role(self.current_user, members) in ("owner", "np-owner"): return self.forbidden() # Enabling and disabling service accounts via the group endpoints is forbidden # because we need the preserve_membership data that is only available via the # UserEnable form. if is_service_account(self.session, group=group): return self.forbidden() group.enable() self.session.commit() AuditLog.log(self.session, self.current_user.id, 'enable_group', 'Enabled group.', on_group_id=group.id) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None user_id = kwargs.get("user_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserGitHubForm(self.request.arguments) if not form.validate(): return self.render("user-github.html", form=form, user=user, alerts=self.get_form_alerts(form.errors)) new_username = form.data["username"] if new_username == "": new_username = None set_user_metadata(self.session, user.id, USER_METADATA_GITHUB_USERNAME_KEY, new_username) AuditLog.log( self.session, self.current_user.id, "changed_github_username", "Changed GitHub username: {}".format(form.data["username"]), on_user_id=user.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, *args: Any, **kwargs: Any) -> None: mapping_id = int(self.get_path_argument("mapping_id")) mapping = PermissionMap.get(self.session, id=mapping_id) if not mapping: return self.notfound() if not self.check_access(self.session, mapping, self.current_user): return self.forbidden() permission = mapping.permission group = mapping.group mapping.delete(self.session) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log( self.session, self.current_user.id, "revoke_permission", "Revoked permission with argument: {}".format(mapping.argument), on_group_id=group.id, on_permission_id=permission.id, ) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def notify_nonauditor_flagged(settings, session, edge): """Send notification that a nonauditor in an audited group has had their membership set to expire. Handles email notification and audit logging. Args: settings (Settings): Grouper Settings object for current run. session (Session): Object for db session. edge (GroupEdge): The expiring edge. """ # Pull data about the edge and the affected user group_name = edge.group.name assert OBJ_TYPES_IDX[edge.member_type] == "User" user = User.get(session, pk=edge.member_pk) member_name = user.username recipients = [member_name] audit_data = { "action": "nonauditor_flagged", "actor_id": user.id, "description": "Flagged {} as nonauditor approver in audited group".format(member_name), } AuditLog.log(session, on_user_id=user.id, on_group_id=edge.group_id, **audit_data) email_context = {"group_name": group_name, "member_name": member_name} send_email( session=session, recipients=recipients, subject="Membership in {} set to expire".format(group_name), template="nonauditor", settings=settings, context=email_context, )
def post(self, user_id=None, name=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() try: if user.role_user: disable_role_user(self.session, user=user) else: disable_user(self.session, user) except PluginRejectedDisablingUser as e: alert = Alert("danger", str(e)) return self.redirect("/users/{}".format(user.name), alerts=[alert]) self.session.commit() AuditLog.log(self.session, self.current_user.id, 'disable_user', 'Disabled user.', on_user_id=user.id) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, user_id=None, name=None, key_id=None, tag_id=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() try: key = get_public_key(self.session, user.id, key_id) except KeyNotFound: return self.notfound() tag = PublicKeyTag.get(self.session, id=tag_id) if not tag: return self.notfound() try: remove_tag_from_public_key(self.session, key, tag) except TagNotOnKey: return self.redirect("/users/{}?refresh=yes".format(user.name)) AuditLog.log(self.session, self.current_user.id, 'untag_public_key', 'Untagged public key: {}'.format(key.fingerprint), on_tag_id=tag.id, on_user_id=user.id) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self): form = TagCreateForm(self.request.arguments) if not form.validate(): return self.render( "tag-create.html", form=form, alerts=self.get_form_alerts(form.errors) ) tag = PublicKeyTag( name=form.data["tagname"], description=form.data["description"], ) try: tag.add(self.session) self.session.flush() except IntegrityError: self.session.rollback() form.tagname.errors.append( "{} already exists".format(form.data["tagname"]) ) return self.render( "tag-create.html", form=form, alerts=self.get_form_alerts(form.errors) ) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log(self.session, self.current_user.id, 'create_tag', 'Created new tag.', on_tag_id=tag.id) return self.redirect("/tags/{}?refresh=yes".format(tag.name))
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None user_id = kwargs.get("user_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserShellForm(self.request.arguments) form.shell.choices = settings().shell if not form.validate(): return self.render( "user-shell.html", form=form, user=user, alerts=self.get_form_alerts(form.errors) ) set_user_metadata(self.session, user.id, USER_METADATA_SHELL_KEY, form.data["shell"]) AuditLog.log( self.session, self.current_user.id, "changed_shell", "Changed shell: {}".format(form.data["shell"]), on_user_id=user.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, user_id=None, name=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = PublicKeyForm(self.request.arguments) if not form.validate(): return self.render( "public-key-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) try: pubkey = public_key.add_public_key(self.session, user, form.data["public_key"]) except public_key.DuplicateKey: form.public_key.errors.append( "Key already in use. Public keys must be unique.") except public_key.PublicKeyParseError: form.public_key.errors.append("Public key appears to be invalid.") except public_key.BadPublicKey as e: form.public_key.errors.append(str(e)) if form.public_key.errors: return self.render( "public-key-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) AuditLog.log( self.session, self.current_user.id, "add_public_key", "Added public key: {}".format(pubkey.fingerprint_sha256), on_user_id=user.id, ) email_context = { "actioner": self.current_user.name, "changed_user": user.name, "action": "added", } send_email( self.session, [user.name], "Public SSH key added", "ssh_keys_changed", settings, email_context, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self): can_create = user_creatable_permissions(self.session, self.current_user) if not can_create: return self.forbidden() form = PermissionCreateForm(self.request.arguments) if not form.validate(): return self.render("permission-create.html", form=form, alerts=self.get_form_alerts(form.errors)) # A user is allowed to create a permission if the name matches any of the globs that they # are given access to via PERMISSION_CREATE, as long as the permission does not match a # reserved name. (Unless specifically granted.) allowed = False for creatable in can_create: if matches_glob(creatable, form.data["name"]): allowed = True for failure_message in test_reserved_names(form.data["name"]): form.name.errors.append(failure_message) if not allowed: form.name.errors.append( "Permission name does not match any of your allowed patterns.") if form.name.errors: return self.render("permission-create.html", form=form, alerts=self.get_form_alerts(form.errors)) try: permission = create_permission(self.session, form.data["name"], form.data["description"]) self.session.flush() except IntegrityError: self.session.rollback() form.name.errors.append( "Name already in use. Permissions must be unique.") return self.render( "permission-create.html", form=form, can_create=can_create, alerts=self.get_form_alerts(form.errors), ) self.session.commit() AuditLog.log( self.session, self.current_user.id, "create_permission", "Created permission.", on_permission_id=permission.id, ) # No explicit refresh because handler queries SQL. return self.redirect("/permissions/{}".format(permission.name))
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None user_id = kwargs.get("user_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserTokenForm(self.request.arguments) if not form.validate(): return self.render( "user-token-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) try: token, secret = add_new_user_token( self.session, UserToken(name=form.data["name"], user=user)) self.session.commit() except IntegrityError: self.session.rollback() form.name.errors.append("Name already in use.") return self.render( "user-token-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) AuditLog.log( self.session, self.current_user.id, "add_token", "Added token: {}".format(token.name), on_user_id=user.id, ) email_context = { "actioner": self.current_user.name, "changed_user": user.name, "action": "added", } send_email( self.session, [user.name], "User token created", "user_tokens_changed", settings(), email_context, ) return self.render("user-token-created.html", token=token, secret=secret)
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None user_id = kwargs.get("user_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserTokenForm(self.request.arguments) if not form.validate(): return self.render( "user-token-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) try: token, secret = add_new_user_token( self.session, UserToken(name=form.data["name"], user=user) ) self.session.commit() except IntegrityError: self.session.rollback() form.name.errors.append("Name already in use.") return self.render( "user-token-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) AuditLog.log( self.session, self.current_user.id, "add_token", "Added token: {}".format(token.name), on_user_id=user.id, ) email_context = { "actioner": self.current_user.name, "changed_user": user.name, "action": "added", } send_email( self.session, [user.name], "User token created", "user_tokens_changed", settings(), email_context, ) return self.render("user-token-created.html", token=token, secret=secret)
def post(self): can_create = user_creatable_permissions(self.session, self.current_user) if not can_create: return self.forbidden() form = PermissionCreateForm(self.request.arguments) if not form.validate(): return self.render( "permission-create.html", form=form, alerts=self.get_form_alerts(form.errors) ) # A user is allowed to create a permission if the name matches any of the globs that they # are given access to via PERMISSION_CREATE, as long as the permission does not match a # reserved name. (Unless specifically granted.) allowed = False for creatable in can_create: if matches_glob(creatable, form.data["name"]): allowed = True for failure_message in test_reserved_names(form.data["name"]): form.name.errors.append(failure_message) if not allowed: form.name.errors.append("Permission name does not match any of your allowed patterns.") if form.name.errors: return self.render( "permission-create.html", form=form, alerts=self.get_form_alerts(form.errors) ) try: permission = create_permission( self.session, form.data["name"], form.data["description"] ) self.session.flush() except IntegrityError: self.session.rollback() form.name.errors.append("Name already in use. Permissions must be unique.") return self.render( "permission-create.html", form=form, can_create=sorted(can_create), alerts=self.get_form_alerts(form.errors), ) self.session.commit() AuditLog.log( self.session, self.current_user.id, "create_permission", "Created permission.", on_permission_id=permission.id, ) # No explicit refresh because handler queries SQL. return self.redirect("/permissions/{}".format(permission.name))
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None user_id = kwargs.get("user_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] user = User.get(self.session, user_id, name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() form = UserPasswordForm(self.request.arguments) if not form.validate(): return self.render( "user-password-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) pass_name = form.data["name"] password = form.data["password"] try: add_new_user_password(self.session, pass_name, password, user.id) except PasswordAlreadyExists: self.session.rollback() form.name.errors.append("Name already in use.") return self.render( "user-password-add.html", form=form, user=user, alerts=self.get_form_alerts(form.errors), ) AuditLog.log( self.session, self.current_user.id, "add_password", "Added password: {}".format(pass_name), on_user_id=user.id, ) email_context = { "actioner": self.current_user.name, "changed_user": user.name, "pass_name": pass_name, } send_email( self.session, [user.name], "User password created", "user_password_created", settings(), email_context, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") group = Group.get(self.session, name=name) if not group: return self.notfound() if not user_can_manage_group(self.session, group, self.current_user): return self.forbidden() form = GroupEditForm(self.request.arguments, obj=group) if not form.validate(): return self.render("group-edit.html", group=group, form=form, alerts=self.get_form_alerts(form.errors)) new_name = form.data["groupname"] renamed = group.groupname != new_name if renamed and is_role_user(self.session, group=group): form.groupname.errors.append( "You cannot change the name of service account groups") return self.render("group-edit.html", group=group, form=form, alerts=self.get_form_alerts(form.errors)) if renamed and Group.get(self.session, name=new_name): message = f"A group named '{new_name}' already exists (possibly disabled)" form.groupname.errors.append(message) return self.render("group-edit.html", group=group, form=form, alerts=self.get_form_alerts(form.errors)) group.groupname = new_name group.email_address = form.data["email_address"] group.description = form.data["description"] group.canjoin = form.data["canjoin"] group.auto_expire = form.data["auto_expire"] group.require_clickthru_tojoin = form.data["require_clickthru_tojoin"] Counter.incr(self.session, "updates") self.session.commit() AuditLog.log(self.session, self.current_user.id, "edit_group", "Edited group.", on_group_id=group.id) url = f"/groups/{group.name}" if renamed: url += "?refresh=yes" self.redirect(url)
def post(self, group_id=None, name=None, account_id=None, accountname=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() service_account = ServiceAccount.get(self.session, account_id, accountname) if not service_account: return self.notfound() user = service_account.user if not self.check_access(self.session, self.current_user, service_account): return self.forbidden() grantable = group.my_permissions() form = self.get_form(grantable) if not form.validate(): return self.render( "service-account-permission-grant.html", form=form, user=user, group=group, alerts=self.get_form_alerts(form.errors) ) permission = Permission.get(self.session, form.data["permission"]) if not permission: return self.notfound() allowed = False for perm in grantable: if perm[1] == permission.name: if matches_glob(perm[3], form.data["argument"]): allowed = True break if not allowed: form.argument.errors.append( "The group {} does not have that permission".format(group.name)) return self.render( "service-account-permission-grant.html", form=form, user=user, group=group, alerts=self.get_form_alerts(form.errors) ) try: grant_permission_to_service_account( self.session, service_account, permission, form.data["argument"]) except IntegrityError: self.session.rollback() return self.render( "service-account-permission-grant.html", form=form, user=user, alerts=self.get_form_alerts(form.errors) ) AuditLog.log(self.session, self.current_user.id, "grant_permission", "Granted permission with argument: {}".format(form.data["argument"]), on_permission_id=permission.id, on_group_id=group.id, on_user_id=service_account.user.id) return self.redirect("/groups/{}/service/{}?refresh=yes".format( group.name, service_account.user.username))
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() if not user_can_manage_group(self.session, group, self.current_user): return self.forbidden() form = GroupRemoveForm(self.request.arguments) if not form.validate(): return self.send_error(status_code=400) member_type, member_name = form.data["member_type"], form.data["member"] members = group.my_members() if not members.get((member_type.capitalize(), member_name), None): return self.notfound() removed_member = get_user_or_group(self.session, member_name, user_or_group=member_type) if self.current_user == removed_member: return self.send_error( status_code=400, reason="Can't remove yourself. Leave group instead." ) role_user = is_role_user(self.session, group=group) if (role_user and get_role_user(self.session, group=group).user.name == removed_member.name): return self.send_error( status_code=400, reason="Can't remove a service account user from the service account group." ) try: group.revoke_member( self.current_user, removed_member, "Removed by owner/np-owner/manager" ) AuditLog.log(self.session, self.current_user.id, 'remove_from_group', '{} was removed from the group.'.format(removed_member.name), on_group_id=group.id, on_user_id=removed_member.id) except PluginRejectedGroupMembershipUpdate as e: alert = Alert("danger", str(e)) if role_user: return self.redirect("/service/{}".format(group.name), alerts=[alert]) else: return self.redirect("/groups/{}".format(group.name), alerts=[alert]) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def enable_service_account(session, actor, service_account, owner): # type: (Session, User, ServiceAccount, Group) -> None """Enables a service account and sets a new owner.""" enable_user(session, service_account.user, actor, preserve_membership=False) add_service_account(session, owner, service_account) AuditLog.log(session, actor.id, "enable_service_account", "Enabled service account.", on_group_id=owner.id, on_user_id=service_account.user_id) Counter.incr(session, "updates") session.commit()
def post(self, user_id=None, name=None, token_id=None): user = User.get(self.session, user_id, name) if not user: return self.notfound() if (user.name != self.current_user.name) and not self.current_user.user_admin: return self.forbidden() token = UserToken.get(self.session, user=user, id=token_id) disable_user_token(self.session, token) AuditLog.log(self.session, self.current_user.id, 'disable_token', 'Disabled token: {}'.format(token.name), on_user_id=user.id) self.session.commit() return self.render("user-token-disabled.html", token=token)
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() members = group.my_members() if not self.current_user.my_role(members) in ("owner", "np-owner"): return self.forbidden() group.enable() self.session.commit() AuditLog.log(self.session, self.current_user.id, 'enable_group', 'Enabled group.', on_group_id=group.id) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def disable_service_account(session, actor, service_account): # type: (Session, User, ServiceAccount) -> None """Disables a service account and deletes the association with a Group.""" disable_user(session, service_account.user) owner_id = service_account.owner.group.id service_account.owner.delete(session) permissions = session.query(ServiceAccountPermissionMap).filter_by( service_account_id=service_account.id) for permission in permissions: permission.delete(session) AuditLog.log(session, actor.id, "disable_service_account", "Disabled service account.", on_group_id=owner_id, on_user_id=service_account.user_id) Counter.incr(session, "updates") session.commit()
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() members = group.my_members() if not user_role(self.current_user, members): return self.forbidden() group.revoke_member(self.current_user, self.current_user, "User self-revoked.") AuditLog.log(self.session, self.current_user.id, 'leave_group', '{} left the group.'.format(self.current_user.name), on_group_id=group.id) return self.redirect("/groups/{}?refresh=yes".format(group.name))