def test_user_status_changes(session, tmpdir, users, groups): # noqa: F811 username = "******" groupname = "team-sre" # add user to a group call_main(session, tmpdir, "group", "add_member", "--member", groupname, username) # disable the account call_main(session, tmpdir, "user", "disable", username) assert not User.get(session, name=username).enabled # double disabling is a no-op call_main(session, tmpdir, "user", "disable", username) assert not User.get(session, name=username).enabled # re-enable the account, preserving memberships call_main(session, tmpdir, "user", "enable", "--preserve-membership", username) assert User.get(session, name=username).enabled assert (u"User", username) in groups[groupname].my_members() # enabling an active account is a no-op call_main(session, tmpdir, "user", "enable", username) assert User.get(session, name=username).enabled # disable and re-enable without the --preserve-membership flag call_main(session, tmpdir, "user", "disable", username) call_main(session, tmpdir, "user", "enable", username) assert User.get(session, name=username).enabled assert (u"User", username) not in groups[groupname].my_members()
def test_add_service_account(session, users, http_client, base_url): user = users['*****@*****.**'] # Add account fe_url = url(base_url, '/service/create') resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'name': '*****@*****.**', "description": "Hi", "canjoin": "canjoin"}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 assert User.get(session, name="*****@*****.**") is None assert Group.get(session, name="*****@*****.**") is None # Add account fe_url = url(base_url, '/service/create') resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'name': '*****@*****.**', "description": "Hi", "canjoin": "canjoin"}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_service_account(session, user=u) assert is_service_account(session, group=g) assert get_service_account(session, user=u).group.id == g.id assert get_service_account(session, group=g).user.id == u.id assert not is_service_account(session, user=user) assert not is_service_account(session, group=Group.get(session, name="team-sre"))
def test_sa_pubkeys(session, users, http_client, base_url): user = users['*****@*****.**'] # Add account create_role_user(session, user, '*****@*****.**', 'Hi', 'canjoin') u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre")) assert not get_public_keys_of_user(session, user.id) with pytest.raises(HTTPError): # add it fe_url = url(base_url, '/users/{}/public-key/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'public_key': SSH_KEY_1}), headers={'X-Grouper-User': "******"}) # add it fe_url = url(base_url, '/users/{}/public-key/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'public_key': SSH_KEY_1}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 # add bad key -- shouldn't add fe_url = url(base_url, '/users/{}/public-key/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'public_key': SSH_KEY_BAD}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 sa = User.get(session, name="*****@*****.**") keys = get_public_keys_of_user(session, sa.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_1 with pytest.raises(HTTPError): # delete it fe_url = url(base_url, '/users/{}/public-key/{}/delete'.format("*****@*****.**", keys[0].id)) resp = yield http_client.fetch(fe_url, method="POST", body='', headers={'X-Grouper-User': "******"}) # delete it fe_url = url(base_url, '/users/{}/public-key/{}/delete'.format("*****@*****.**", keys[0].id)) resp = yield http_client.fetch(fe_url, method="POST", body='', headers={'X-Grouper-User': user.username}) assert resp.code == 200 sa = User.get(session, name="*****@*****.**") assert not get_public_keys_of_user(session, sa.id)
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 create_user(self, name): # type: (str) -> None """Create a user, does nothing if it already exists.""" if User.get(self.session, name=name): return user = User(username=name) user.add(self.session)
def test_public_key(session, users, http_client, base_url): user = users['*****@*****.**'] assert not get_public_keys_of_user(session, user.id) # add it fe_url = url(base_url, '/users/{}/public-key/add'.format(user.username)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'public_key': SSH_KEY_1}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 user = User.get(session, name=user.username) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_1 assert keys[0].fingerprint == 'e9:ae:c5:8f:39:9b:3a:9c:6a:b8:33:6b:cb:6f:ba:35' assert keys[0].fingerprint_sha256 == 'MP9uWaujW96EWxbjDtPdPWheoMDu6BZ8FZj0+CBkVWU' assert keys[0].comment == 'some-comment' # delete it fe_url = url(base_url, '/users/{}/public-key/{}/delete'.format(user.username, keys[0].id)) resp = yield http_client.fetch(fe_url, method="POST", body='', headers={'X-Grouper-User': user.username}) assert resp.code == 200 user = User.get(session, name=user.username) assert not get_public_keys_of_user(session, user.id)
def test_public_key_admin(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] assert not get_public_keys_of_user(session, user.id) # add it fe_url = url(base_url, "/users/{}/public-key/add".format(user.username)) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"public_key": SSH_KEY_1}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 user = User.get(session, name=user.username) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_1 # have an admin delete it fe_url = url(base_url, "/users/{}/public-key/{}/delete".format(user.username, keys[0].id)) resp = yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": "******"} ) assert resp.code == 200 user = User.get(session, name=user.username) assert not get_public_keys_of_user(session, user.id)
def test_public_key(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] assert not get_public_keys_of_user(session, user.id) # add it fe_url = url(base_url, "/users/{}/public-key/add".format(user.username)) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"public_key": SSH_KEY_ED25519}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 user = User.get(session, name=user.username) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_ED25519 assert keys[0].fingerprint == "fa:d9:ca:40:bd:f7:64:37:a7:99:3a:8e:50:8a:c5:94" assert keys[0].fingerprint_sha256 == "ExrCZ0nqSJv+LqAEh8CWeKUxiAeZA+N0bKC18dK7Adg" assert keys[0].comment == "comment" # delete it fe_url = url(base_url, "/users/{}/public-key/{}/delete".format(user.username, keys[0].id)) resp = yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": user.username} ) assert resp.code == 200 user = User.get(session, name=user.username) assert not get_public_keys_of_user(session, user.id)
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 test_user_public_key(make_session, session, users): make_session.return_value = session # good key username = '******' good_key = ('ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCUQeasspT/etEJR2WUoR+h2sMOQYbJgr0Q' 'E+J8p97gEhmz107KWZ+3mbOwyIFzfWBcJZCEg9wy5Paj+YxbGONqbpXAhPdVQ2TLgxr41bNXvbcR' 'AxZC+Q12UZywR4Klb2kungKz4qkcmSZzouaKK12UxzGB3xQ0N+3osKFj3xA1+B6HqrVreU19XdVo' 'AJh0xLZwhw17/NDM+dAcEdMZ9V89KyjwjraXtOVfFhQF0EDF0ame8d6UkayGrAiXC2He0P2Cja+J' '371P27AlNLHFJij8WGxvcGGSeAxMLoVSDOOllLCYH5UieV8mNpX1kNe2LeA58ciZb0AXHaipSmCH' 'gh/ some-comment') call_main('user', 'add_public_key', username, good_key) user = User.get(session, name=username) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == good_key # bad key username = '******' bad_key = 'ssh-rsa AAAblahblahkey some-comment' call_main('user', 'add_public_key', username, good_key) user = User.get(session, name=username) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == good_key
def test_user_status_changes(make_user_session, make_group_session, session, users, groups): make_user_session.return_value = session make_group_session.return_value = session username = '******' groupname = 'team-sre' # add user to a group call_main('group', 'add_member', '--member', groupname, username) # disable the account call_main('user', 'disable', username) assert not User.get(session, name=username).enabled # double disabling is a no-op call_main('user', 'disable', username) assert not User.get(session, name=username).enabled # re-enable the account, preserving memberships call_main('user', 'enable', '--preserve-membership', username) assert User.get(session, name=username).enabled assert (u'User', username) in groups[groupname].my_members() # enabling an active account is a no-op call_main('user', 'enable', username) assert User.get(session, name=username).enabled # disable and re-enable without the --preserve-membership flag call_main('user', 'disable', username) call_main('user', 'enable', username) assert User.get(session, name=username).enabled assert (u'User', username) not in groups[groupname].my_members()
def users(session): users = { username: User.get_or_create(session, username=username)[0] for username in ("*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**") } users["*****@*****.**"] = User.get_or_create(session, username="******", role_user=True)[0] session.commit() return users
def run(self, session, **kwargs): if kwargs.get("group"): Group.get_or_create(session, groupname=groupname) session.commit() elif kwargs.get("key") == "valuewith=": User.get_or_create(session, username=other_username) session.commit() else: User.get_or_create(session, username=username) session.commit()
def run(self, session, **kwargs): if kwargs.get('group'): Group.get_or_create(session, groupname=groupname) session.commit() elif kwargs.get('key') == 'valuewith=': User.get_or_create(session, username=other_username) session.commit() else: User.get_or_create(session, username=username) session.commit()
def create_role_user(self, role_user, description="", join_policy=GroupJoinPolicy.CAN_ASK): # type: (str, str, GroupJoinPolicy) -> None """Create an old-style role user. This concept is obsolete and all code related to it will be deleted once all remaining legacy role users have been converted to service accounts. This method should be used only for tests to maintain backward compatibility until that happens. """ user = User(username=role_user, role_user=True) user.add(self.session) self.create_group(role_user, description, join_policy) self.add_user_to_group(role_user, role_user)
def test_user_public_key(make_session, session, users): make_session.return_value = session # good key username = '******' call_main('user', 'add_public_key', username, SSH_KEY_1) user = User.get(session, name=username) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_1 # duplicate key call_main('user', 'add_public_key', username, SSH_KEY_1) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_1 # bad key call_main('user', 'add_public_key', username, SSH_KEY_BAD) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_1
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 get_current_user(self): username = self.request.headers.get(settings.user_auth_header) if not username: return # Users must be fully qualified if not re.match("^{}$".format(USERNAME_VALIDATION), username): raise InvalidUser() try: user, created = User.get_or_create(self.session, username=username) if created: logging.info("Created new user %s", username) self.session.commit() # Because the graph doesn't initialize until the updates table # is populated, we need to refresh the graph here in case this # is the first update. self.graph.update_from_db(self.session) except sqlalchemy.exc.OperationalError: # Failed to connect to database or create user, try to reconfigure the db. This invokes # the fetcher to try to see if our URL string has changed. Session.configure(bind=get_db_engine(get_database_url(settings))) raise DatabaseFailure() return user
def get_role_user(session, user=None, group=None): # type: (Session, User, Group) -> RoleUser """ Takes in a User or a Group and returns a dictionary that contains all of the service account components for the service account that the user/group is part of. Args: session: the database session user: a User object to check group: a Group object to check Throws: RoleUserNotFound: if the user or group is not part of a service account Returns: a dictionary with all components of the service account of the user or group passed in """ if not is_role_user(session, user, group): raise RoleUserNotFound() if user: name = user.name else: assert group is not None name = group.name return RoleUser(User.get(session, name=name), Group.get(session, name=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 = 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, 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 role(self, role): prev_role = self._role self._role = GROUP_EDGE_ROLES.index(role) # Groups should always "member". if not (OBJ_TYPES_IDX[self.member_type] == "User"): return # If ownership status is unchanged, no notices need to be adjusted. if (self._role in OWNER_ROLE_INDICES) == (prev_role in OWNER_ROLE_INDICES): return recipient = User.get(self.session, pk=self.member_pk).username expiring_supergroups = self.group.my_expiring_groups() member_name = self.group.name if role in ["owner", "np-owner"]: # We're creating a new owner, who should find out when this group # they now own loses its membership in larger groups. for supergroup_name, expiration in expiring_supergroups: AsyncNotification.add_expiration(self.session, expiration, group_name=supergroup_name, member_name=member_name, recipients=[recipient], member_is_user=False) else: # We're removing an owner, who should no longer find out when this # group they no longer own loses its membership in larger groups. for supergroup_name, _ in expiring_supergroups: AsyncNotification.cancel_expiration(self.session, group_name=supergroup_name, member_name=member_name, recipients=[recipient])
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 is_role_user(session, user=None, group=None): # type: (Session, User, Group) -> bool """ Takes in a User or a Group and returns a boolean indicating whether that User/Group is a component of a service account. Args: session: the database session user: a User object to check group: a Group object to check Throws: AssertionError if neither a user nor a group is provided Returns: whether the User/Group is a component of a service account """ if user is not None: return user.role_user assert group is not None user = User.get(session, name=group.groupname) if not user: return False return user.role_user
def post(self): supplied_token = self.get_body_argument("token") match = TokenValidate.validator.match(supplied_token) if not match: return self.error(((1, "Token format not recognized"),)) sess = Session() token_name = match.group("token_name") token_secret = match.group("token_secret") owner = User.get(sess, name=match.group("name")) token = UserToken.get(sess, owner, token_name) if token is None: return self.error(((2, "Token specified does not exist"),)) if not token.enabled: return self.error(((3, "Token is disabled"),)) if not token.check_secret(token_secret): return self.error(((4, "Token secret mismatch"),)) return self.success({ "owner": owner.username, "identity": str(token), "act_as_owner": True, "valid": True, })
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 cancel_user_request(self, user_request, reason, authorization): # type: (UserGroupRequest, str, Authorization) -> None now = datetime.utcnow() request = Request.get(self.session, id=user_request.id) if not request: raise UserGroupRequestNotFoundException(request) actor = User.get(self.session, name=authorization.actor) if not actor: raise UserNotFoundException(authorization.actor) request_status_change = RequestStatusChange( request=request, user_id=actor.id, from_status=request.status, to_status="cancelled", change_at=now, ).add(self.session) request.status = "cancelled" self.session.flush() Comment( obj_type=OBJ_TYPES["RequestStatusChange"], obj_pk=request_status_change.id, user_id=actor.id, comment=reason, created_on=now, ).add(self.session)
def get(self, user_id=None, name=None): self.handle_refresh() user = User.get(self.session, user_id, name) if not user: return self.notfound() if user.role_user: return self.redirect("/service/{}".format(user_id or name)) if user.is_service_account: service_account = user.service_account if service_account.owner: return self.redirect("/groups/{}/service/{}".format( service_account.owner.group.name, user.username)) else: self.render( "service-account.html", service_account=service_account, group=None, user=user, **get_user_view_template_vars(self.session, self.current_user, user, self.graph) ) return self.render("user.html", user=user, **get_user_view_template_vars(self.session, self.current_user, user, self.graph) )
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 test_sa_tokens(session, users, http_client, base_url): user = users['*****@*****.**'] # Add account create_role_user(session, user, '*****@*****.**', 'Hi', 'canjoin') u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre")) with pytest.raises(HTTPError): # Add token fe_url = url(base_url, '/users/{}/tokens/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'name': 'myDHDToken'}), headers={'X-Grouper-User': "******"}) # Add token fe_url = url(base_url, '/users/{}/tokens/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'name': 'myDHDToken'}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 # Verify add fe_url = url(base_url, '/users/{}'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="GET", headers={'X-Grouper-User': user.username}) assert resp.code == 200 assert "Added token: myDHDToken" in resp.body with pytest.raises(HTTPError): # Disable token fe_url = url(base_url, '/users/{}/tokens/1/disable'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body="", headers={'X-Grouper-User': "******"}) # Disable token fe_url = url(base_url, '/users/{}/tokens/1/disable'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body="", headers={'X-Grouper-User': user.username}) assert resp.code == 200 # Verify disable fe_url = url(base_url, '/users/{}'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="GET", headers={'X-Grouper-User': user.username}) assert resp.code == 200 assert "Disabled token: myDHDToken" in resp.body
def promote_nonauditors(self, session): # type: (Session) -> None """Checks all enabled audited groups and ensures that all approvers for that group have the PERMISSION_AUDITOR permission. All non-auditor approvers of audited groups will be promoted to be auditors, i.e., added to the auditors group. Args: session (Session): database session """ graph = Graph() # Hack to ensure the graph is loaded before we access it graph.update_from_db(session) # map from user object to names of audited groups in which # user is a nonauditor approver nonauditor_approver_to_groups = defaultdict( set) # type: Dict[User, Set[str]] user_is_auditor = {} # type: Dict[str, bool] for group_tuple in graph.get_groups(audited=True, directly_audited=False): group_md = graph.get_group_details(group_tuple.groupname, expose_aliases=False) for username, user_md in iteritems(group_md["users"]): if username not in user_is_auditor: user_perms = graph.get_user_details( username)["permissions"] user_is_auditor[username] = any([ p["permission"] == PERMISSION_AUDITOR for p in user_perms ]) if user_is_auditor[username]: # user is already auditor so can skip continue if user_md["role"] in APPROVER_ROLE_INDICES: # non-auditor approver. BAD! nonauditor_approver_to_groups[username].add( group_tuple.groupname) if nonauditor_approver_to_groups: auditors_group = get_auditors_group(self.settings, session) for username, group_names in iteritems( nonauditor_approver_to_groups): reason = "auto-added due to having approver role(s) in group(s): {}".format( ", ".join(group_names)) user = User.get(session, name=username) assert user auditors_group.add_member(user, user, reason, status="actioned") notify_nonauditor_promoted(self.settings, session, user, auditors_group, group_names) session.commit()
def test_user_create(session, tmpdir, users): # noqa: F811 # simple username = "******" call_main(session, tmpdir, "user", "create", username) assert User.get(session, name=username), "non-existent user should be created" # check username bad_username = "******" call_main(session, tmpdir, "user", "create", bad_username) assert not User.get(session, name=bad_username), "bad user should not be created" # bulk usernames = ["*****@*****.**", "*****@*****.**", "*****@*****.**"] call_main(session, tmpdir, "user", "create", *usernames) users = [User.get(session, name=u) for u in usernames] assert all(users), "all users created" usernames_with_one_bad = ["*****@*****.**", "*****@*****.**", "not_valid_user"] call_main(session, tmpdir, "user", "create", *usernames_with_one_bad) users = [User.get(session, name=u) for u in usernames_with_one_bad] assert not any(users), "one bad seed means no users created"
def get(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() form.shell.choices = settings.shell self.render("user-shell.html", form=form, user=user)
def test_github(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] assert get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is None user = User.get(session, name=user.username) fe_url = url(base_url, "/users/{}/github".format(user.username)) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"username": "******"}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 user = User.get(session, name=user.username) assert (get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is not None) assert (get_user_metadata_by_key( session, user.id, USER_METADATA_GITHUB_USERNAME_KEY).data_value == "joe-on-github") audit_entries = AuditLog.get_entries(session, on_user_id=user.id, action="changed_github_username") assert len(audit_entries) == 1 assert audit_entries[ 0].description == "Changed GitHub username: joe-on-github" fe_url = url(base_url, "/users/{}/github".format(user.username)) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"username": ""}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 user = User.get(session, name=user.username) assert get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is None
def mutate_group_command(session, group, args): # type: (Session, Group, Namespace) -> None for username in args.username: user = User.get(session, name=username) if not user: logging.error("no such user '{}'".format(username)) return if args.subcommand == "add_member": if args.member: role = "member" elif args.owner: role = "owner" elif args.np_owner: role = "np-owner" elif args.manager: role = "manager" assert role logging.info("Adding {} as {} to group {}".format( username, role, args.groupname)) group.add_member(user, user, "grouper-ctl join", status="actioned", role=role) AuditLog.log( session, user.id, "join_group", "{} manually joined via grouper-ctl".format(username), on_group_id=group.id, ) session.commit() elif args.subcommand == "remove_member": logging.info("Removing {} from group {}".format( username, args.groupname)) try: group.revoke_member(user, user, "grouper-ctl remove") AuditLog.log( session, user.id, "leave_group", "{} manually left via grouper-ctl".format(username), on_group_id=group.id, ) session.commit() except PluginRejectedGroupMembershipUpdate as e: logging.error("%s", e)
def get(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") token_id = int(self.get_path_argument("token_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() token = UserToken.get(self.session, user=user, id=token_id) return self.render("user-token-disable.html", user=user, token=token)
def get(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() self.render("user-token-add.html", form=UserTokenForm(), user=user)
def get(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() self.render("user-password-add.html", form=UserPasswordForm(), user=user)
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(e.message) 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 permission_grants_for_user(self, username): # type: (str) -> List[PermissionGrant] now = datetime.utcnow() user = User.get(self.session, name=username) if not user or user.role_user or user.is_service_account or not user.enabled: return [] # Get the groups of which this user is a direct member. groups = (self.session.query(Group.id).join( GroupEdge, Group.id == GroupEdge.group_id).join( User, User.id == GroupEdge.member_pk).filter( Group.enabled == True, User.id == user.id, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["User"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ).distinct()) group_ids = [g.id for g in groups] # Now, get the parent groups of those groups and so forth until we run out of levels of the # tree. Use a set of seen group_ids to avoid querying the same group twice if a user is a # member of it via multiple paths. seen_group_ids = set(group_ids) while group_ids: parent_groups = (self.session.query(Group.id).join( GroupEdge, Group.id == GroupEdge.group_id).filter( GroupEdge.member_pk.in_(group_ids), Group.enabled == True, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["Group"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ).distinct()) group_ids = [ g.id for g in parent_groups if g.id not in seen_group_ids ] seen_group_ids.update(group_ids) # Return the permission grants. group_permission_grants = (self.session.query( Permission.name, PermissionMap.argument).filter( Permission.id == PermissionMap.permission_id, PermissionMap.group_id.in_(seen_group_ids), ).all()) return [ PermissionGrant(g.name, g.argument) for g in group_permission_grants ]
def test_user_status_changes( make_user_session, make_group_session, session, users, groups # noqa: F811 ): make_user_session.return_value = session make_group_session.return_value = session username = "******" groupname = "team-sre" # add user to a group call_main(session, "group", "add_member", "--member", groupname, username) # disable the account call_main(session, "user", "disable", username) assert not User.get(session, name=username).enabled # double disabling is a no-op call_main(session, "user", "disable", username) assert not User.get(session, name=username).enabled # re-enable the account, preserving memberships call_main(session, "user", "enable", "--preserve-membership", username) assert User.get(session, name=username).enabled assert (u"User", username) in groups[groupname].my_members() # enabling an active account is a no-op call_main(session, "user", "enable", username) assert User.get(session, name=username).enabled # disable and re-enable without the --preserve-membership flag call_main(session, "user", "disable", username) call_main(session, "user", "enable", username) assert User.get(session, name=username).enabled assert (u"User", username) not in groups[groupname].my_members()
def get(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() form.shell.choices = settings().shell self.render("user-shell.html", form=form, user=user)
def get(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) except KeyNotFound: return self.notfound() self.render("public-key-delete.html", user=user, key=key)
def get(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) return self.render("user-password-delete.html", user=user, password=password)
def get_group(self, name): # type: (str) -> Optional[Group] group = SQLGroup.get(self.session, name=name) if not group: return None user = SQLUser.get(self.session, name=name) is_role_user = user.role_user if user else False return Group( name=group.groupname, description=group.description, email_address=group.email_address, join_policy=GroupJoinPolicy(group.canjoin), enabled=group.enabled, is_role_user=is_role_user, )
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 not self.check_access(self.session, self.current_user, user): 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 grant_permission_to_service_account(self, permission, argument, service_account): # type: (str, str, str) -> None self.create_permission(permission) permission_obj = Permission.get(self.session, name=permission) assert permission_obj user_obj = User.get(self.session, name=service_account) assert user_obj, "Must create the service account first" assert user_obj.is_service_account grant = ServiceAccountPermissionMap( permission_id=permission_obj.id, service_account_id=user_obj.service_account.id, argument=argument, ) grant.add(self.session)
def test_public_key(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] assert not get_public_keys_of_user(session, user.id) # add it fe_url = url(base_url, "/users/{}/public-key/add".format(user.username)) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"public_key": SSH_KEY_ED25519}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 user = User.get(session, name=user.username) keys = get_public_keys_of_user(session, user.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_ED25519 assert keys[ 0].fingerprint == "fa:d9:ca:40:bd:f7:64:37:a7:99:3a:8e:50:8a:c5:94" assert keys[ 0].fingerprint_sha256 == "ExrCZ0nqSJv+LqAEh8CWeKUxiAeZA+N0bKC18dK7Adg" assert keys[0].comment == "comment" # delete it fe_url = url( base_url, "/users/{}/public-key/{}/delete".format(user.username, keys[0].id)) resp = yield http_client.fetch(fe_url, method="POST", body="", headers={"X-Grouper-User": user.username}) assert resp.code == 200 user = User.get(session, name=user.username) assert not get_public_keys_of_user(session, user.id)
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() metadata_key = self.get_path_argument("key") if metadata_key == USER_METADATA_SHELL_KEY: return self.redirect("/users/{}/shell".format(user.name)) elif metadata_key == USER_METADATA_GITHUB_USERNAME_KEY: return self.redirect("/github/link_begin/{}".format(user.id)) known_field = metadata_key in settings().metadata_options metadata_item = get_user_metadata_by_key(self.session, user.id, metadata_key) if not metadata_item and not known_field: return self.notfound() form = UserMetadataForm(self.request.arguments) form.value.choices = settings().metadata_options.get( metadata_key, DEFAULT_METADATA_OPTIIONS) if not form.validate(): return self.render( "user-metadata.html", form=form, user=user, metadata_key=metadata_key, is_enabled=known_field, alerts=self.get_form_alerts(form.errors), ) set_user_metadata(self.session, user.id, metadata_key, form.data["value"]) AuditLog.log( self.session, self.current_user.id, "changed_user_metadata", "Changed {}: {}".format(metadata_key, form.data["value"]), on_user_id=user.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def get(self, user_id=None, name=None): self.handle_refresh() user = User.get(self.session, user_id, name) if not user or not user.role_user: return self.notfound() group = Group.get(self.session, name=name) actor = self.current_user graph = self.graph session = self.session self.render("service.html", user=user, group=group, **get_role_user_view_template_vars(session, actor, user, group, graph))
def test_user_created_plugin(session, users, groups): """Test calls to the user_created plugin.""" plugin = UserCreatedPlugin() grouper.plugin.Plugins = [plugin] # Create a regular user. The service account flag should be false, and the plugin should be # called. user, created = User.get_or_create(session, username="******") assert created == True assert plugin.calls == 1 # Create a role user. This should cause another plugin call and the service account flag # should now be true. plugin.expected_service_account = True create_role_user(session, user, "*****@*****.**", "description", "canask") assert plugin.calls == 2
def get(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() form.shell.choices = settings().shell self.render("user-shell.html", form=form, user=user)
def add_public_key_to_user(self, key, user): # type: (str, str) -> None sql_user = User.get(self.session, name=user) assert sql_user public_key = SSHKey(key, strict=True) public_key.parse() sql_public_key = PublicKey( user_id=sql_user.id, public_key=public_key.keydata.strip(), fingerprint=public_key.hash_md5().replace("MD5:", ""), fingerprint_sha256=public_key.hash_sha256().replace("SHA256:", ""), key_size=public_key.bits, key_type=public_key.key_type, comment=public_key.comment, ) sql_public_key.add(self.session)
def add_user_to_group(self, user, group, role="member"): # type: (str, str, str) -> None self.create_user(user) self.create_group(group) user_obj = User.get(self.session, name=user) assert user_obj group_obj = Group.get(self.session, name=group) assert group_obj edge = GroupEdge( group_id=group_obj.id, member_type=OBJ_TYPES["User"], member_pk=user_obj.id, active=True, _role=GROUP_EDGE_ROLES.index(role), ) edge.add(self.session)
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 = 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: 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() 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, ) if user.role_user: group = Group.get(self.session, name=user.username) if group and group.audit: # complete the audit group.audit.complete = True self.session.commit() cancel_async_emails(self.session, f"audit-{group.id}") AuditLog.log( self.session, self.current_user.id, "complete_audit", "Disabling group completes group audit.", on_group_id=group.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def create_group_request(self, user, group, role="member"): # type: (str, str, str) -> None self.create_user(user) self.create_group(group) user_obj = User.get(self.session, name=user) assert user_obj group_obj = Group.get(self.session, name=group) assert group_obj # Note: despite the function name, this only creates the request. The flow here is # convoluted enough that it seems best to preserve exact behavior for testing. group_obj.add_member(requester=user_obj, user_or_group=user_obj, reason="", status="pending", role=role)
def test_add_role_user(session, users, http_client, base_url): user = users['*****@*****.**'] # Add account create_role_user(session, user, '*****@*****.**', 'Hi', 'canjoin') u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre"))