async def list_catalog_records( record_id: str, auth: Authorized = Depends(Authorize(ODPScope.RECORD_READ)), paginator: Paginator = Depends(), ): if not (record := Session.get(Record, record_id)): raise HTTPException(HTTP_404_NOT_FOUND)
def _delete_record( record_id: str, auth: Authorized, ignore_collection_tags: bool = False, ) -> None: if not (record := Session.get(Record, record_id)): raise HTTPException(HTTP_404_NOT_FOUND)
async def tag_record( record_id: str, tag_instance_in: TagInstanceModelIn, tag_schema: JSONSchema = Depends(get_tag_schema), auth: Authorized = Depends(TagAuthorize()), ): if not (record := Session.get(Record, record_id)): raise HTTPException(HTTP_404_NOT_FOUND)
def _untag_record( record_id: str, tag_instance_id: str, auth: Authorized, ignore_user_id: bool = False, ) -> None: if not (record := Session.get(Record, record_id)): raise HTTPException(HTTP_404_NOT_FOUND)
async def get_catalog_record( record_id: str, catalog_id: str, auth: Authorized = Depends(Authorize(ODPScope.RECORD_READ)), ): if not (catalog_record := Session.get(CatalogRecord, (catalog_id, record_id))): raise HTTPException(HTTP_404_NOT_FOUND)
def login_callback(self): """Save the token and log the user in.""" token = self.oauth.hydra.authorize_access_token() userinfo = self.oauth.hydra.userinfo() user_id = userinfo['sub'] if not (token_model := Session.get(OAuth2Token, (self.client_id, user_id))): token_model = OAuth2Token(client_id=self.client_id, user_id=user_id)
async def update_collection( collection_in: CollectionModelIn, auth: Authorized = Depends(Authorize(ODPScope.COLLECTION_ADMIN)), ): if auth.collection_ids != '*' and collection_in.id not in auth.collection_ids: raise HTTPException(HTTP_403_FORBIDDEN) if not (collection := Session.get(Collection, collection_in.id)): raise HTTPException(HTTP_404_NOT_FOUND)
async def update_client( client_in: ClientModelIn, auth: Authorized = Depends(Authorize(ODPScope.CLIENT_ADMIN)), ): if auth.collection_ids != '*' and client_in.collection_id not in auth.collection_ids: raise HTTPException(HTTP_403_FORBIDDEN) if not (client := Session.get(Client, client_in.id)): raise HTTPException(HTTP_404_NOT_FOUND)
async def update_role( role_in: RoleModelIn, auth: Authorized = Depends(Authorize(ODPScope.ROLE_ADMIN)), ): if auth.collection_ids != '*' and role_in.collection_id not in auth.collection_ids: raise HTTPException(HTTP_403_FORBIDDEN) if not (role := Session.get(Role, role_in.id)): raise HTTPException(HTTP_404_NOT_FOUND)
def init_roles(): """Create or update role definitions.""" with open(datadir / 'roles.yml') as f: role_data = yaml.safe_load(f) for role_id, role_spec in role_data.items(): role = Session.get(Role, role_id) or Role(id=role_id) role.scopes = [Session.get(Scope, (scope_id, ScopeType.odp)) for scope_id in role_spec['scopes']] role.save()
async def get_new_doi( collection_id: str, auth: Authorized = Depends(Authorize(ODPScope.COLLECTION_READ)), ): if auth.collection_ids != '*' and collection_id not in auth.collection_ids: raise HTTPException(HTTP_403_FORBIDDEN) if not (collection := Session.get(Collection, collection_id)): raise HTTPException(HTTP_404_NOT_FOUND)
def _untag_collection( collection_id: str, tag_instance_id: str, auth: Authorized, ignore_user_id: bool = False, ) -> None: if auth.collection_ids != '*' and collection_id not in auth.collection_ids: raise HTTPException(HTTP_403_FORBIDDEN) if not (collection := Session.get(Collection, collection_id)): raise HTTPException(HTTP_404_NOT_FOUND)
async def update_record( record_id: str, record_in: RecordModelIn, metadata_schema: JSONSchema = Depends(get_metadata_schema), auth: Authorized = Depends(Authorize(ODPScope.RECORD_WRITE)), ): if auth.collection_ids != '*' and record_in.collection_id not in auth.collection_ids: raise HTTPException(HTTP_403_FORBIDDEN) if not (record := Session.get(Record, record_id)): raise HTTPException(HTTP_404_NOT_FOUND)
async def tag_collection( collection_id: str, tag_instance_in: TagInstanceModelIn, tag_schema: JSONSchema = Depends(get_tag_schema), auth: Authorized = Depends(TagAuthorize()), ): if auth.collection_ids != '*' and collection_id not in auth.collection_ids: raise HTTPException(HTTP_403_FORBIDDEN) if not (collection := Session.get(Collection, collection_id)): raise HTTPException(HTTP_404_NOT_FOUND)
def init_cli_client(): """Create or update the Swagger UI client.""" client = Session.get(Client, ODP_CLI_CLIENT_ID) or Client(id=ODP_CLI_CLIENT_ID) client.scopes = [Session.get(Scope, (s.value, ScopeType.odp)) for s in ODPScope] client.save() hydra_admin_api.create_or_update_client( id=ODP_CLI_CLIENT_ID, name=ODP_CLI_CLIENT_NAME, secret=ODP_CLI_CLIENT_SECRET, scope_ids=[s.value for s in ODPScope], grant_types=[GrantType.CLIENT_CREDENTIALS], )
async def create_project( project_in: ProjectModelIn, ): if Session.get(Project, project_in.id): raise HTTPException(HTTP_409_CONFLICT, 'Project id is already in use') project = Project( id=project_in.id, name=project_in.name, collections=[ Session.get(Collection, collection_id) for collection_id in project_in.collection_ids ], ) project.save()
def validate_auto_login(user_id): """ Validate a login request for which Hydra has indicated that the user is already authenticated, returning the user object on success. An ``ODPIdentityError`` is raised if the login cannot be permitted for any reason. :param user_id: the user id :raises ODPUserNotFound: if the user account associated with this id has been deleted :raises ODPAccountLocked: if the user account has been temporarily locked :raises ODPAccountDisabled: if the user account has been deactivated :raises ODPEmailNotVerified: if the user changed their email address since their last login, but have not yet verified it """ user = Session.get(User, user_id) if not user: raise x.ODPUserNotFound if is_account_locked(user_id): raise x.ODPAccountLocked if not user.active: raise x.ODPAccountDisabled if not user.verified: raise x.ODPEmailNotVerified
def init_dap_client(): """Create or update the Data Access Portal client.""" client = Session.get(Client, ODP_UI_DAP_CLIENT_ID) or Client(id=ODP_UI_DAP_CLIENT_ID) client.scopes = [Session.get(Scope, (HydraScope.OPENID, ScopeType.oauth))] + \ [Session.get(Scope, (HydraScope.OFFLINE_ACCESS, ScopeType.oauth))] client.save() hydra_admin_api.create_or_update_client( id=ODP_UI_DAP_CLIENT_ID, name=ODP_UI_DAP_CLIENT_NAME, secret=ODP_UI_DAP_CLIENT_SECRET, scope_ids=[HydraScope.OPENID, HydraScope.OFFLINE_ACCESS], grant_types=[GrantType.AUTHORIZATION_CODE, GrantType.REFRESH_TOKEN], response_types=[ResponseType.CODE], redirect_uris=[ODP_UI_DAP_LOGGED_IN_URL], post_logout_redirect_uris=[ODP_UI_DAP_LOGGED_OUT_URL], )
def _select_records(self) -> list[tuple[str, datetime]]: """Select records to be evaluated for publication to, or retraction from, a catalog. A record is selected if: * there is no corresponding catalog_record entry; or * the record has any embargo tags; or * catalog_record.timestamp is less than any of the following: * catalog.schema.timestamp * collection.timestamp * record.timestamp :return: a list of (record_id, timestamp) tuples, where timestamp is that of the latest contributing change """ catalog = Session.get(Catalog, self.catalog_id) records_subq = ( select( Record.id.label('record_id'), func.greatest( catalog.schema.timestamp, Collection.timestamp, Record.timestamp, ).label('max_timestamp') ). join(Collection). subquery() ) catalog_records_subq = ( select( CatalogRecord.record_id, CatalogRecord.timestamp ). where(CatalogRecord.catalog_id == self.catalog_id). subquery() ) stmt = ( select( records_subq.c.record_id, records_subq.c.max_timestamp ). outerjoin_from(records_subq, catalog_records_subq). where(or_( catalog_records_subq.c.record_id == None, catalog_records_subq.c.timestamp < records_subq.c.max_timestamp, catalog_records_subq.c.record_id.in_( select(RecordTag.record_id). where(RecordTag.tag_id == ODPRecordTag.EMBARGO) ) )) ) return Session.execute(stmt).all()
def get_user_profile(user_id): """ Return a dict of user profile info. """ user = Session.get(User, user_id) info = {} for attr in 'name', 'picture': info[attr] = getattr(user, attr) return info
async def create_provider(provider_in: ProviderModelIn, ): if Session.get(Provider, provider_in.id): raise HTTPException(HTTP_409_CONFLICT, 'Provider id is already in use') provider = Provider( id=provider_in.id, name=provider_in.name, ) provider.save()
def update_user_verified(user_id, verified): """ Update the verified status of a user. :param user_id: the user id :param verified: True/False """ user = Session.get(User, user_id) user.verified = verified user.save()
def update_user_password(user_id, password): """ Update a user's password. :param user_id: the user id :param password: the input plain-text password """ user = Session.get(User, user_id) user.password = ph.hash(password) user.save()
def init_catalogs(): """Create or update catalog definitions.""" with open(datadir / 'catalogs.yml') as f: catalog_data = yaml.safe_load(f) for catalog_id in (catalog_ids := [c.value for c in ODPCatalog]): catalog_spec = catalog_data[catalog_id] catalog = Session.get(Catalog, catalog_id) or Catalog(id=catalog_id) catalog.schema_id = catalog_spec['schema_id'] catalog.schema_type = SchemaType.catalog catalog.save()
def new_generic_tag(cardinality): return TagFactory( type='record', cardinality=cardinality, scope=Session.get(Scope, (ODPScope.RECORD_QC, ScopeType.odp)) or Scope(id=ODPScope.RECORD_QC, type=ScopeType.odp), schema=SchemaFactory( type='tag', uri='https://odp.saeon.ac.za/schema/tag/generic', ), )
def new_generic_tag(cardinality): # we can use any scope; just make it something other than COLLECTION_ADMIN return TagFactory( type='collection', cardinality=cardinality, scope=Session.get(Scope, (ODPScope.COLLECTION_READ, ScopeType.odp)) or Scope(id=ODPScope.COLLECTION_READ, type=ScopeType.odp), schema=SchemaFactory( type='tag', uri='https://odp.saeon.ac.za/schema/tag/generic', ), )
def _evaluate_record(self, record_id: str, timestamp: datetime) -> bool: """Evaluate a record model (API) against the publication schema for a catalog, and commit the result to the catalog_record table. The catalog_record entry is stamped with the `timestamp` of the latest contributing change (from catalog/record/record_tag/collection_tag). """ catalog = Session.get(Catalog, self.catalog_id) record = Session.get(Record, record_id) catalog_record = (Session.get(CatalogRecord, (self.catalog_id, record_id)) or CatalogRecord(catalog_id=self.catalog_id, record_id=record_id)) record_model = output_record_model(record) record_json = JSON(record_model.dict()) publication_schema = schema_catalog.get_schema(URI(catalog.schema.uri)) if (result := publication_schema.evaluate(record_json)).valid: catalog_record.validity = result.output('flag') catalog_record.published = True catalog_record.published_record = self._create_published_record(record_model).dict() self._save_published_doi(record_model)
def update_user_profile(user_id, **userinfo): """ Update optional user profile info. Only update user attributes that are supplied in the dict. :param user_id: the user id :param userinfo: dict containing profile info """ user = Session.get(User, user_id) for attr in 'name', 'picture': if attr in userinfo: setattr(user, attr, userinfo[attr]) user.save()
async def list_published_records( catalog_id: str, paginator: Paginator = Depends(), ): if not Session.get(Catalog, catalog_id): raise HTTPException(HTTP_404_NOT_FOUND) stmt = (select(CatalogRecord).where( CatalogRecord.catalog_id == catalog_id).where(CatalogRecord.published)) paginator.sort = 'record_id' return paginator.paginate( stmt, lambda row: PublishedRecordModel(**row.CatalogRecord.published_record), )
def init_tags(): """Create or update tag definitions.""" with open(datadir / 'tags.yml') as f: tag_data = yaml.safe_load(f) for tag_id in (tag_ids := [t.value for t in ODPRecordTag] + [t.value for t in ODPCollectionTag]): tag_spec = tag_data[tag_id] tag_type = tag_spec['type'] tag = Session.get(Tag, (tag_id, tag_type)) or Tag(id=tag_id, type=tag_type) tag.cardinality = tag_spec['cardinality'] tag.public = tag_spec['public'] tag.scope_id = tag_spec['scope_id'] tag.scope_type = ScopeType.odp tag.schema_id = tag_spec['schema_id'] tag.schema_type = SchemaType.tag tag.save()