def test_remove_owner(self): # Add test_user1 and test_user2 as group owner graph_user1 = GraphUser(identifier=str(self.scim_user1.scim_id), display_name='Test User 1') graph_user2 = GraphUser(identifier=str(self.scim_user2.scim_id), display_name='Test User 2') self.scim_group1.owners = [graph_user1, graph_user2] self.app.scimapi_groupdb.save(self.scim_group1) # Check that test_user2 is an owner of scim_group1 group = self.app.scimapi_groupdb.get_group_by_scim_id(str(self.scim_group1.scim_id)) assert group.has_owner(self.scim_user2.scim_id) is True with self.session_cookie(self.browser, self.test_user.eppn) as client: with client.session_transaction() as sess: with self.app.test_request_context(): data = { 'group_identifier': str(self.scim_group1.scim_id), 'user_identifier': str(self.scim_user2.scim_id), 'role': 'owner', 'csrf_token': sess.get_csrf_token(), } response = client.post('/remove-user', data=json.dumps(data), content_type=self.content_type_json) self._check_success_response(response, type_='POST_GROUP_MANAGEMENT_REMOVE_USER_SUCCESS') payload = response.json.get('payload') assert 0 == len(payload['member_of']) assert 1 == len(payload['owner_of']) # Check that test_user2 is no longer a member of scim_group1 group = self.app.scimapi_groupdb.get_group_by_scim_id(str(self.scim_group1.scim_id)) assert group.has_owner(self.scim_user2.scim_id) is False
def accept_group_invitation(scim_user: ScimApiUser, scim_group: ScimApiGroup, invite: GroupInviteState) -> None: graph_user = GraphUser(identifier=str(scim_user.scim_id), display_name=invite.email_address) modified = False if invite.role == GroupRole.OWNER: if not is_owner(scim_user, scim_group.scim_id): scim_group.add_owner(graph_user) modified = True elif invite.role == GroupRole.MEMBER: if not is_member(scim_user, scim_group.scim_id): scim_group.add_member(graph_user) modified = True else: raise NotImplementedError(f'Unknown role: {invite.role}') if modified: if not current_app.scimapi_groupdb.save(scim_group): current_app.logger.error( f'Failed to save group with scim_id: {invite.group_scim_id}') raise EduIDDBError('Failed to save group') current_app.logger.info( f'Added user as {invite.role.value} to group with scim_id: {invite.group_scim_id}' ) return None
def test_invite_owner(self): # Add test user as group owner graph_user = GraphUser( identifier=str(self.scim_user1.scim_id), display_name=self.test_user.mail_addresses.primary.email ) self.scim_group1.owners = [graph_user] self.app.scimapi_groupdb.save(self.scim_group1) # Invite test user 2 to the group as owner response = self._invite( group_scim_id=str(self.scim_group1.scim_id), inviter=self.test_user, invite_address=self.test_user2.mail_addresses.primary.email, role='owner', ) payload = response.json.get('payload') outgoing = payload['outgoing'] assert 1 == len(outgoing) for invite in outgoing: assert str(self.scim_group1.scim_id) == invite['group_identifier'] assert 0 == len(invite['member_invites']) assert 1 == len(invite['owner_invites']) assert ( self.app.invite_state_db.get_state( group_scim_id=str(self.scim_group1.scim_id), email_address=self.test_user2.mail_addresses.primary.email, role=GroupRole.OWNER, ) is not None )
def test_remove_last_owner(self): # Add test_user1 as group owner graph_user1 = GraphUser(identifier=str(self.scim_user1.scim_id), display_name='Test User 1') self.scim_group1.owners = [graph_user1] self.app.scimapi_groupdb.save(self.scim_group1) # Check that test_user1 is an owner of scim_group1 group = self.app.scimapi_groupdb.get_group_by_scim_id(str(self.scim_group1.scim_id)) found_owners = [owner for owner in group.graph.owners if owner.identifier == str(self.scim_user1.scim_id)] assert 1 == len(found_owners) with self.session_cookie(self.browser, self.test_user.eppn) as client: with client.session_transaction() as sess: with self.app.test_request_context(): data = { 'group_identifier': str(self.scim_group1.scim_id), 'user_identifier': str(self.scim_user1.scim_id), 'role': 'owner', 'csrf_token': sess.get_csrf_token(), } response = client.post('/remove-user', data=json.dumps(data), content_type=self.content_type_json) self._check_error_response(response, type_='POST_GROUP_MANAGEMENT_REMOVE_USER_FAIL') # Check that test_user1 is still owner of scim_group1 group = self.app.scimapi_groupdb.get_group_by_scim_id(str(self.scim_group1.scim_id)) found_owners = [owner for owner in group.graph.owners if owner.identifier == str(self.scim_user1.scim_id)] assert 1 == len(found_owners)
def add_member(self, group: ScimApiGroup, member: Union[ScimApiUser, ScimApiGroup], display_name: str) -> ScimApiGroup: if isinstance(member, ScimApiUser): member = GraphUser(identifier=str(member.scim_id), display_name=display_name) elif isinstance(member, ScimApiGroup): member = GraphGroup(identifier=str(member.scim_id), display_name=display_name) group.add_member(member) assert self.groupdb # mypy doesn't know setUp will be called self.groupdb.save(group) return group
def create_group(user: User, display_name: str) -> FluxData: scim_user = get_or_create_scim_user_by_eppn(user.eppn) graph_user = GraphUser(identifier=str(scim_user.scim_id), display_name=user.mail_addresses.primary.email) group = ScimApiGroup(display_name=display_name) group.owners = [graph_user] group.members = [graph_user] if not current_app.scimapi_groupdb.save(group): current_app.logger.error( f'Failed to create ScimApiGroup with scim_id: {group.scim_id}') return error_response(message=CommonMsg.temp_problem) current_app.logger.info( f'Created ScimApiGroup with scim_id: {group.scim_id}') current_app.stats.count(name='group_created') return get_groups()
def test_delete_group_not_owner(self): # Add test user as group member graph_user = GraphUser( identifier=str(self.scim_user1.scim_id), display_name=self.test_user.mail_addresses.primary.email ) self.scim_group1.members = [graph_user] self.app.scimapi_groupdb.save(self.scim_group1) with self.session_cookie(self.browser, self.test_user.eppn) as client: with client.session_transaction() as sess: with self.app.test_request_context(): data = {'group_identifier': str(self.scim_group1.scim_id), 'csrf_token': sess.get_csrf_token()} response = client.post('/delete', data=json.dumps(data), content_type=self.content_type_json) self._check_error_response( response, type_='POST_GROUP_MANAGEMENT_DELETE_FAIL', msg=GroupManagementMsg.user_not_owner ) assert self.app.scimapi_groupdb.group_exists(str(self.scim_group1.scim_id)) is True
def test_get_groups(self): # Add test user as group member and owner graph_user = GraphUser( identifier=str(self.scim_user1.scim_id), display_name=self.test_user.mail_addresses.primary.email ) self.scim_group1.members = [graph_user] self.scim_group1.owners = [graph_user] self.app.scimapi_groupdb.save(self.scim_group1) response = self.browser.get('/groups') assert response.status_code == 302 # Redirect to token service with self.session_cookie(self.browser, self.test_user.eppn) as client: response = client.get('/groups') self._check_success_response(response, type_='GET_GROUP_MANAGEMENT_GROUPS_SUCCESS') payload = response.json.get('payload') assert 1 == len(payload['member_of']) assert 1 == len(payload['owner_of']) assert 'Test Group 1' == payload['member_of'][0]['display_name'] assert 'Test Group 1' == payload['owner_of'][0]['display_name']
def test_delete_group_and_invites(self): # Add test user as group owner graph_user = GraphUser( identifier=str(self.scim_user1.scim_id), display_name=self.test_user.mail_addresses.primary.email ) self.scim_group1.owners = [graph_user] self.app.scimapi_groupdb.save(self.scim_group1) self._invite_setup() assert 3 == len(self.app.invite_state_db.get_states_by_group_scim_id(str(self.scim_group1.scim_id))) with self.session_cookie(self.browser, self.test_user.eppn) as client: with client.session_transaction() as sess: with self.app.test_request_context(): data = {'group_identifier': str(self.scim_group1.scim_id), 'csrf_token': sess.get_csrf_token()} response = client.post('/delete', data=json.dumps(data), content_type=self.content_type_json) self._check_success_response(response, type_='POST_GROUP_MANAGEMENT_DELETE_SUCCESS') assert self.app.scimapi_groupdb.group_exists(str(self.scim_group1.scim_id)) is False with self.assertRaises(DocumentDoesNotExist): self.app.invite_state_db.get_states_by_group_scim_id(str(self.scim_group1.scim_id))
def test_delete_group(self): # Add test user as group owner of two groups graph_user = GraphUser( identifier=str(self.scim_user1.scim_id), display_name=self.test_user.mail_addresses.primary.email ) self.scim_group1.owners = [graph_user] self.app.scimapi_groupdb.save(self.scim_group1) self.scim_group2.owners = [graph_user] self.app.scimapi_groupdb.save(self.scim_group2) with self.session_cookie(self.browser, self.test_user.eppn) as client: with client.session_transaction() as sess: with self.app.test_request_context(): data = {'group_identifier': str(self.scim_group1.scim_id), 'csrf_token': sess.get_csrf_token()} response = client.post('/delete', data=json.dumps(data), content_type=self.content_type_json) self._check_success_response(response, type_='POST_GROUP_MANAGEMENT_DELETE_SUCCESS') payload = response.json.get('payload') assert 0 == len(payload['member_of']) assert 1 == len(payload['owner_of']) assert self.app.scimapi_groupdb.group_exists(str(self.scim_group2.scim_id)) is True assert self.app.scimapi_groupdb.group_exists(str(self.scim_group1.scim_id)) is False
def _invite_setup(self): # Add test user as group owner of two groups graph_user = GraphUser( identifier=str(self.scim_user1.scim_id), display_name=self.test_user.mail_addresses.primary.email ) self.scim_group1.owners = [graph_user] self.app.scimapi_groupdb.save(self.scim_group1) self.scim_group2.owners = [graph_user] self.app.scimapi_groupdb.save(self.scim_group2) # Invite test_user2 as owner and member of Test Group 1 self._invite( group_scim_id=str(self.scim_group1.scim_id), inviter=self.test_user, invite_address=self.test_user2.mail_addresses.primary.email, role='member', ) self._invite( group_scim_id=str(self.scim_group1.scim_id), inviter=self.test_user, invite_address=self.test_user2.mail_addresses.primary.email, role='owner', ) # Invite test_user3 as member of Test Group 1 self._invite( group_scim_id=str(self.scim_group1.scim_id), inviter=self.test_user, invite_address=self.test_user3.mail_addresses.primary.email, role='member', ) # Invite test_user3 as member of Test Group 2 self._invite( group_scim_id=str(self.scim_group2.scim_id), inviter=self.test_user, invite_address=self.test_user3.mail_addresses.primary.email, role='member', )
def test_decline_invite_owner(self): # Add test user as group owner graph_user = GraphUser( identifier=str(self.scim_user1.scim_id), display_name=self.test_user.mail_addresses.primary.email ) self.scim_group1.owners = [graph_user] self.app.scimapi_groupdb.save(self.scim_group1) # Invite test user 2 to the group as member self._invite( group_scim_id=str(self.scim_group1.scim_id), inviter=self.test_user, invite_address=self.test_user2.mail_addresses.primary.email, role='owner', ) # Decline invite as test user 2 response = self._decline_invite( group_scim_id=str(self.scim_group1.scim_id), invitee=self.test_user2, invite_address=self.test_user2.mail_addresses.primary.email, role='owner', ) payload = response.json.get('payload') incoming = payload['incoming'] assert 0 == len(incoming) with self.assertRaises(DocumentDoesNotExist): self.app.invite_state_db.get_state( group_scim_id=str(self.scim_group1.scim_id), email_address=self.test_user2.mail_addresses.primary.email, role=GroupRole.MEMBER, ) scim_group = self.app.scimapi_groupdb.get_group_by_scim_id(str(self.scim_group1.scim_id)) scim_user = self.app.scimapi_userdb.get_user_by_external_id( f'{self.test_user2.eppn}@{self.app.config.scim_external_id_scope}' ) assert scim_group.has_owner(scim_user.scim_id) is False
def test_get_all_data_privacy(self): # Add test user as group member and owner, add test user 2 as member graph_user1 = GraphUser( identifier=str(self.scim_user1.scim_id), display_name=self.test_user.mail_addresses.primary.email ) graph_user2 = GraphUser( identifier=str(self.scim_user2.scim_id), display_name=self.test_user2.mail_addresses.primary.email ) self.scim_group1.members = [graph_user1, graph_user2] self.scim_group1.owners = [graph_user1] self.app.scimapi_groupdb.save(self.scim_group1) # Invite test user 2 as owner self._invite( group_scim_id=str(self.scim_group1.scim_id), inviter=self.test_user, invite_address=self.test_user2.mail_addresses.primary.email, role='owner', ) # Get all data as test user 1 with self.session_cookie(self.browser, self.test_user.eppn) as client: response = client.get('/all-data') self._check_success_response(response, type_='GET_GROUP_MANAGEMENT_ALL_DATA_SUCCESS') payload = response.json.get('payload') # As member the user only see owners for a group assert normalised_data( [ { 'display_name': 'Test Group 1', 'identifier': '00000000-0000-0000-0000-000000000002', 'members': [], 'owners': [ {'display_name': '*****@*****.**', 'identifier': '00000000-0000-0000-0000-000000000000'} ], } ] ) == normalised_data(payload['member_of']) # As owner the user see both members and owners assert normalised_data( [ { 'display_name': 'Test Group 1', 'identifier': '00000000-0000-0000-0000-000000000002', 'members': [ {'display_name': '*****@*****.**', 'identifier': '00000000-0000-0000-0000-000000000000'}, { 'display_name': '*****@*****.**', 'identifier': '00000000-0000-0000-0000-000000000001', }, ], 'owners': [ {'display_name': '*****@*****.**', 'identifier': '00000000-0000-0000-0000-000000000000'} ], } ] ) == normalised_data(payload['owner_of']) # As owner the user see your outgoing invites assert normalised_data( [ { 'group_identifier': '00000000-0000-0000-0000-000000000002', 'member_invites': [], 'owner_invites': [{'email_address': '*****@*****.**'}], } ] ) == normalised_data(payload['outgoing']) # test user 1 does not have any incoming invites assert [] == normalised_data(payload['incoming']) # Get all data as test user 2 with self.session_cookie(self.browser, self.test_user2.eppn) as client: response = client.get('/all-data') self._check_success_response(response, type_='GET_GROUP_MANAGEMENT_ALL_DATA_SUCCESS') payload = response.json.get('payload') # As member the user only see owners for a group assert normalised_data( [ { 'display_name': 'Test Group 1', 'identifier': '00000000-0000-0000-0000-000000000002', 'members': [], 'owners': [ {'display_name': '*****@*****.**', 'identifier': '00000000-0000-0000-0000-000000000000'} ], } ] ) == normalised_data(payload['member_of']) # test user 2 is not an owner of a group assert [] == payload['owner_of'] # test user 2 does not have any outgoing invites assert [] == payload['outgoing'] # as an invitee the user see incoming invites assert normalised_data( [ { 'display_name': 'Test Group 1', 'email_address': '*****@*****.**', 'group_identifier': '00000000-0000-0000-0000-000000000002', 'owners': [ {'display_name': '*****@*****.**', 'identifier': '00000000-0000-0000-0000-000000000000'} ], 'role': 'owner', } ] ) == normalised_data(payload['incoming'])
def update_group(self, update_request: GroupUpdateRequest, db_group: ScimApiGroup) -> Tuple[ScimApiGroup, bool]: changed = False updated_members = set() logger.info(f'Updating group {str(db_group.scim_id)}') for this in update_request.members: if this.is_user: _member = db_group.graph.get_member_user(identifier=str(this.value)) _new_member = None if _member else GraphUser(identifier=str(this.value), display_name=this.display) elif this.is_group: _member = db_group.graph.get_member_group(identifier=str(this.value)) _new_member = None if _member else GraphGroup(identifier=str(this.value), display_name=this.display) else: raise ValueError(f"Don't recognise member {this}") # Add a new member if _new_member: updated_members.add(_new_member) logger.debug(f'Added new member: {_new_member}') # Update member attributes if they changed elif _member.display_name != this.display: logger.debug(f'Changed display name for existing member: {_member.display_name} -> {this.display}') _member = replace(_member, display_name=this.display) updated_members.add(_member) else: # no change, retain member as-is updated_members.add(_member) if db_group.graph.display_name != update_request.display_name: changed = True db_group.graph = replace(db_group.graph, display_name=update_request.display_name) logger.debug( f'Changed display name for group: {db_group.graph.display_name} -> {update_request.display_name}' ) if db_group.external_id != update_request.external_id: changed = True db_group.external_id = update_request.external_id logger.debug(f'Changed external id for group: {db_group.external_id} -> {update_request.external_id}') # Check if there where new, changed or removed members if db_group.graph.members != updated_members: changed = True db_group.graph = replace(db_group.graph, members=updated_members) logger.debug(f'Old members: {db_group.graph.members}') logger.debug(f'New members: {updated_members}') _sg_ext = GroupExtensions(data=update_request.nutid_group_v1.data) if db_group.extensions != _sg_ext: changed = True db_group.extensions = _sg_ext logger.debug(f'Old extensions: {db_group.extensions}') logger.debug(f'New extensions: {_sg_ext}') if changed: logger.info(f'Group {str(db_group.scim_id)} changed. Saving.') if self.save(db_group): logger.info(f'Group {str(db_group.scim_id)} saved.') else: logger.warning(f'Update of group {db_group} probably failed') return db_group, changed