Skip to content

Commit

Permalink
Group membership fetching working (and tests
Browse files Browse the repository at this point in the history
passing) in invenio-remote-user-data
  • Loading branch information
monotasker committed Aug 18, 2023
1 parent 5687302 commit d701363
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@

REMOTE_USER_DATA_API_ENDPOINTS = {
"local": {
"groups": {
"remote_endpoint": "https://hcommons-staging.org",
"remote_identifier": "email",
"remote_method": "GET",
"token_env_variable_label": "COMMONS_API_TOKEN",
}
},
"knowledgeCommons": {
"groups": {
"remote_endpoint": "https://hcommons-staging.org",
"remote_identifier": "email",
"remote_identifier": "id",
"remote_method": "GET",
"token_env_variable_label": "COMMONS_API_TOKEN",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,65 +22,63 @@ def __init__(self, app, config={}, **kwargs):
self.communities_service = LocalProxy(lambda: app.extensions[
"invenio-communities"].service)

# FIXME: Should we listen to other signals and update more often?
@identity_changed.connect_via(app)
def on_identity_changed(_, identity:Identity) -> None:
"""Update user data from remote server."""
security_datastore = LocalProxy(lambda: app.extensions["security"
].datastore)
my_user = security_datastore.find_user(id=identity.id)
my_idp = 'local'
self.updated_data = {}
if my_user is not None:
my_user_identity = UserIdentity.query.filter_by(
id_user=my_user.id).one_or_none()
# will have a UserIdentity if the user has logged in via an IDP
if my_user_identity is not None:
my_idp = my_user_identity.method
my_remote_id = my_user_identity.id
self.logger.debug(my_idp)
self.logger.debug(my_user_identity.__dict__)

self.updated_data = self.update_data_from_remote(identity, my_user)
self.updated_data = self.update_data_from_remote(identity, my_user, my_idp, my_remote_id)

def update_data_from_remote(self, identity:Identity, user:Optional[User],
**kwargs) -> dict:
idp:str, remote_id:str, **kwargs) -> dict:
"""Main method to update user data from remote server.
"""
self.logger.debug("Updating user data from remote server.")
changed_data = {}
updated_data = {}
# remote_data = self.fetch_from_remote_api(identity, user, **kwargs)
# if remote_data:
# changed_data = self.compare_remote_with_local(identity, user,
# remote_data, **kwargs)
self.logger.debug("communities service:")
matching_communities = self.communities_service.search(
system_identity,
{'q': 'custom_fields.kcr:commons_group_id="mycommunity2"'})
self.logger.debug([c['id'] for c in matching_communities])
# if changed_data:
# updated_data = self.update_local_user_data(identity, user,
# changed_data, **kwargs)

# return updated_data

return {'groups': {'add': ['admin', 'curator']}}
remote_data = self.fetch_from_remote_api(identity, user, idp, remote_id, **kwargs)
if remote_data:
changed_data = self.compare_remote_with_local(user,
remote_data, **kwargs)
if changed_data:
updated_data = self.update_local_user_data(identity, user,
changed_data, **kwargs)

return updated_data

def fetch_from_remote_api(self, identity:Identity, user:User,
idp:str, remote_id:str,
tokens=None, **kwargs) -> dict:
"""Fetch user data for the supplied user from the remote API."""
self.logger.debug(f'fetching user data for identity: {identity}')
self.logger.debug(identity.id)
remote_data = {}

if "groups" in self.config.keys():
groups_config = self.config["groups"]
if "groups" in self.config[idp].keys():
groups_config = self.config[idp]["groups"]

remote_api_token = None
if tokens and "groups" in tokens.keys(): # allows injection for testing
remote_api_token = tokens["groups"]
else:
remote_api_token = os.environ[groups_config["token_env_variable_label"]]

api_url = (f'{groups_config["api_url"]}/'
f'{user[groups_config["remote_identifier"]]}')
if groups_config["remote_identifier"] != "id":
remote_id = getattr(user, groups_config["remote_identifier"])
api_url = (f'{groups_config["remote_endpoint"]}/{remote_id}')

callfuncs = {'GET': requests.get,
'POST': requests.post}
Expand All @@ -89,15 +87,15 @@ def fetch_from_remote_api(self, identity:Identity, user:User,
headers = {}
if remote_api_token:
headers={'Authorization': f'Bearer {remote_api_token}'}

self.logger.debug(f'calling {api_url}')
response = callfunc(api_url, headers=headers, verify=False)
self.logger.debug(pprint(response))
self.logger.debug(pprint(response.json()))

# remote_data['groups'] = {'status_code': response.status_code,
# 'headers': response.headers,
# 'json': response.json(),
# 'text': response.text}
remote_data['groups'] = [response.json()]
remote_data['groups'] = response.json()['groups']

return remote_data

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def test_find_or_create_group(app, users, db):
def test_create_new_group(app, users, db):
"""Test creating a new group role
"""
my_group_role = GroupsComponent(current_remote_user_data_service).create_new_group(group_name='my_group', description='A group for me')
grouper = GroupsComponent(current_remote_user_data_service)
my_group_role = grouper.create_new_group(group_name='my_group',
description='A group for me')
assert my_group_role.name == 'my_group'
assert my_group_role.id == 'my_group'
assert my_group_role.description == 'A group for me'
Expand All @@ -40,16 +42,17 @@ def test_create_new_group(app, users, db):
def test_add_user_to_group(app, users, db):
"""Test adding a user to a group role
"""
user_added = GroupsComponent(
current_remote_user_data_service).add_user_to_group('my_group',
users[0])
grouper = GroupsComponent(current_remote_user_data_service)
my_group_role = grouper.create_new_group(group_name='my_group',
description='A group for me')
user_added = grouper.add_user_to_group('my_group', users[0])
assert user_added == True

# from werkzeug.local import LocalProxy
# security_datastore = LocalProxy(lambda: app.extensions["security"
# ].datastore)
# my_user = security_datastore.find_user(email=users[0].email)
my_user = get_user_by_identifier(users[0].email)
from werkzeug.local import LocalProxy
security_datastore = LocalProxy(lambda: app.extensions["security"
].datastore)
my_user = security_datastore.find_user(email=users[0].email)
# my_user = get_user_by_identifier(users[0].email)
assert 'my_group' in my_user.roles

def test_get_current_members_of_group(app, users, db):
Expand Down
35 changes: 31 additions & 4 deletions site/tests/api/test_invenio_remote_user_data/test_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask_security import login_user, logout_user
from invenio_accounts.models import UserIdentity
from invenio_utilities_tuw.utils import get_identity_for_user
from knowledge_commons_repository.invenio_remote_user_data.components.groups import GroupsComponent
from knowledge_commons_repository.invenio_remote_user_data.proxies import current_remote_user_data_service
Expand All @@ -7,11 +8,35 @@ def test_on_identity_changed(app, users, requests_mock):
"""Test service initialization and signal triggers."""
assert "invenio-remote-user-data" in app.extensions
assert app.extensions["invenio-remote-user-data"].service
base_url = app.config['REMOTE_USER_DATA_API_ENDPOINTS']['local']['groups']['remote_endpoint']
requests_mock.get(f'{base_url}/{users[0].email}', json= {'name': 'awesome-mock'})

# mock the remote api endpoint
base_url = app.config['REMOTE_USER_DATA_API_ENDPOINTS'
]['knowledgeCommons']['groups']['remote_endpoint']
requests_mock.get(f'{base_url}/testuser',
json={'groups': [{'name': 'awesome-mock'},
{'name': 'admin'}]
}
)

# mock SAML login info for the test user and add them to new groups
UserIdentity.create(users[0], "knowledgeCommons", "testuser")
grouper = GroupsComponent(current_remote_user_data_service)
grouper.create_new_group(group_name='cool-group')
grouper.create_new_group(group_name='admin')
grouper.add_user_to_group(group_name='cool-group', user=users[0])
grouper.add_user_to_group(group_name='admin', user=users[0])

# log user in and check whether group memberships were updated
login_user(users[0])
assert current_remote_user_data_service.updated_data == {'groups': ['admin', 'awesome-mock']}

# log user out and check whether group memberships were updated
logout_user()
assert current_remote_user_data_service.updated_data == {'groups': {'awesome-mock': {'name': 'awesome-mock'}}}
assert current_remote_user_data_service.updated_data == {}

# log a different user in without mocking SAML login (so like local)
login_user(users[1])
assert current_remote_user_data_service.updated_data == {}

def test_compare_remote_with_local(app, users):
"""Test comparison of remote and local user data."""
Expand Down Expand Up @@ -54,4 +79,6 @@ def test_update_invenio_group_memberships(app, users, db):

assert actual_updated_memberships == expected_updated_memberships
assert [r for r in users[0].roles] == ['admin', 'awesome-mock']
assert [n.value for n in my_identity.provides] == ['admin', 'awesome-mock']
my_identity = get_identity_for_user(users[0].email)
assert all(n.value for n in my_identity.provides
if n in ['admin', 'awesome-mock', 'any_user', 5])

0 comments on commit d701363

Please sign in to comment.