feat(harvester): use dedicated user with inspire-harvester role - #899
feat(harvester): use dedicated user with inspire-harvester role#899TahaKhan998 wants to merge 1 commit into
Conversation
a765b9a to
7474668
Compare
| "Harvester user is missing the inspire-harvester role " | ||
| "(role id must equal name)." | ||
| ) | ||
| g.cds_harvester_identity = identity |
There was a problem hiding this comment.
could you explain why did you choose to go with a global? it is an approach opposite to what we have normally in invenio
There was a problem hiding this comment.
I cached the harvester user here because get harvester identity is called many times in a harvest run to create, publish, handle files, and search. If it is not cached then get harvester identity is called many times, which I thought could be slow. i removed it now to follow the invenio conventions
7015844 to
91f5439
Compare
|
|
||
| can_create = [AuthenticatedRegularUser(), SystemProcess()] | ||
| # Harvester role can manage records it does not own (legacy system-owned). | ||
| can_manage = RDMRecordPermissionPolicy.can_manage + [InspireHarvester()] |
There was a problem hiding this comment.
why should it be able to manage records?
There was a problem hiding this comment.
so we need to add the inspire harvester generator to the can_manage permission policy because previously many records were harvested by system and, in production, there are records owned by other users and not the inspire harvester user, so for it to be able to update those records with new info from inspire, create new versions of those records, or assign them to the right communities, we need can_manage.
There was a problem hiding this comment.
what about can_curate? isn't it enough? by assigning Harvester to manage you give it also permission to manage the access rights to the record. Doesn't it sound as too much for what the hervester does?
| def get_harvester_user(): | ||
| """Get the configured harvester user.""" | ||
| email = current_app.config.get("CDS_HARVESTER_USER_EMAIL") | ||
| if not email: | ||
| raise HarvesterUserError("CDS_HARVESTER_USER_EMAIL is not configured.") | ||
|
|
||
| user = current_datastore.get_user_by_email(email) | ||
| if user is None: | ||
| raise HarvesterUserError(f"Harvester user '{email}' was not found.") | ||
| if not user.active: | ||
| raise HarvesterUserError(f"Harvester user '{email}' is inactive.") | ||
| return user | ||
|
|
||
|
|
||
| def get_harvester_user_id(): | ||
| """Get the configured harvester user id.""" | ||
| return get_harvester_user().id | ||
|
|
||
|
|
||
| def get_harvester_identity(): | ||
| """Get an identity for harvester operations.""" | ||
| identity = get_identity(get_harvester_user()) | ||
| if inspire_harvester_role not in identity.provides: | ||
| raise HarvesterUserError( | ||
| "Harvester user is missing the inspire-harvester role " | ||
| "(role id must equal name)." | ||
| ) | ||
| return identity |
There was a problem hiding this comment.
how about exploring an alternative implementation of these functions, in a form of a class with specific interface that allows us to retrieve id and identity?
There was a problem hiding this comment.
done i changed it to a class based implementation instead of separate helper functions so now one place handles getting the harvester user id and identity and i instantiate it once and reuse it in the flow so it stays cleaner and easier to maintain
176f381 to
5faefca
Compare
| def user(self): | ||
| """Get the configured harvester user.""" | ||
| email = current_app.config.get("CDS_HARVESTER_USER_EMAIL") | ||
| if not email: | ||
| raise RuntimeError("CDS_HARVESTER_USER_EMAIL is not configured.") | ||
|
|
||
| user = current_datastore.get_user_by_email(email) | ||
| if user is None: | ||
| raise RuntimeError(f"Harvester user '{email}' was not found.") | ||
| if not user.active: | ||
| raise RuntimeError(f"Harvester user '{email}' is inactive.") | ||
| return user |
There was a problem hiding this comment.
implementing the class like this will cause the DB query to be fired up every time you call the method - it is not very optimised approach, also considering that you call this method multiple times.
| from cds_rdm.generators import inspire_harvester_role | ||
|
|
||
|
|
||
| class HarvesterUserService: |
There was a problem hiding this comment.
normally this name is a bit misleading, since this is not really a service in invenio understanding
| class HarvesterUserService: | ||
| """Service for resolving configured harvester user data.""" | ||
|
|
||
| def user(self): |
There was a problem hiding this comment.
now the class starts to resemble something we already have existing which allows us to retrieve users - the users service, check https://github.com/inveniosoftware/invenio-users-resources/blob/master/invenio_users_resources/services/users/service.py
once you retrieve the user object, both id and the identity are reachable from this user object, so we don't need a wrapper class to help us retrieve it again. The only thing which we need is id parameter to be able to read the specific user
| self.harvester_user_service = HarvesterUserService() | ||
| self.identity = self.harvester_user_service.identity() |
There was a problem hiding this comment.
if you use user object mentioned in comments above, then this can be simplified
| self.harvester_user_service = HarvesterUserService() | |
| self.identity = self.harvester_user_service.identity() | |
| self.harvester_user = HarvesterUser().get() |
and later on you call (needs verification on your side)
self.harvester_user.identity
which is also more descriptive in the code, because we get to know of which user identity we are trying to call
| def identity(self): | ||
| """Get an identity for harvester operations.""" | ||
| identity = get_identity(self.user()) | ||
| if inspire_harvester_role not in identity.provides: |
There was a problem hiding this comment.
nit: this check is redundant - we should be informed when harvester tries to do something which it does not have permissions for by PermissionsError
005d448 to
6b94b76
Compare
13f6237 to
9fd8905
Compare
9fd8905 to
f5aa33f
Compare
Closes #881
Moves the INSPIRE harvester off system_identity onto a dedicated service user with the inspire-harvester role, so harvested records have a clear owner. Harvester create/update/publish/search paths now use that identity.