Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/project ownership #346

Merged
merged 43 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e95edea
add projects collection, link to user and folder
teemukataja Jan 28, 2022
a3f3d8f
refactor ownership checking
teemukataja Feb 2, 2022
131c4a4
fix mypy type hint on mongo query object
teemukataja Feb 2, 2022
c974da7
Add filename extraction for objects created from file
genie9 Jan 26, 2022
9149bb6
Add folder patching on object creation
genie9 Jan 26, 2022
04ecce3
Update swagger doc
genie9 Jan 26, 2022
9a78733
Add mypy linting checker for VSCode
genie9 Jan 26, 2022
d557d92
Extract title of object for folder patch
genie9 Feb 9, 2022
8c23e05
Add CSV to submission type check
genie9 Feb 9, 2022
4c23156
Add patching of folder after object update and replace
genie9 Feb 9, 2022
77d3ddb
Update integration tests with folder check for metadata and draft obj…
genie9 Feb 10, 2022
f17496a
Add 'CSV' as accepted submission type
genie9 Feb 11, 2022
48fb095
Update changelog
genie9 Feb 14, 2022
197bd9f
Merge pull request #341 from CSCfi/refactor/patch-object-on-save
blankdots Feb 14, 2022
b8a4860
Bump pytest from 7.0.0 to 7.0.1
dependabot[bot] Feb 14, 2022
2f41465
Bump pip-tools from 6.5.0 to 6.5.1
dependabot[bot] Feb 14, 2022
7b82526
Merge pull request #351 from CSCfi/dependabot/pip/develop/pytest-7.0.1
genie9 Feb 14, 2022
fe06672
Merge pull request #352 from CSCfi/dependabot/pip/develop/pip-tools-6…
genie9 Feb 14, 2022
47e07a3
Merge branch 'develop' into feature/project-ownership
teemukataja Feb 15, 2022
e9b98b3
fix rebase conflicts
teemukataja Feb 15, 2022
fea7e1a
deprecate user ownership functions
teemukataja Feb 15, 2022
4f4453f
update changelog
teemukataja Feb 15, 2022
bc71bd8
make project check in auth more reliable
teemukataja Feb 15, 2022
7c5612e
update wordlist
teemukataja Feb 15, 2022
00cec8f
update integration tests and code respectively, deprecate some features
teemukataja Feb 16, 2022
99e03a0
remove some deprecated code, add deprecation flags to uncertain bits …
teemukataja Feb 16, 2022
6830c2e
fix typo and update part of api spec
teemukataja Feb 16, 2022
2202e3b
add deprecation notes for later pruning, remove some deprecated featu…
teemukataja Feb 17, 2022
19b0f12
fix mypy and spellcheck
teemukataja Feb 17, 2022
890fbff
add templates-endpoint
teemukataja Feb 23, 2022
a523f65
remove deprecations
teemukataja Feb 23, 2022
500af51
Merge branch 'develop' into feature/project-ownership
teemukataja Feb 23, 2022
bbf9cb0
add templates endpoints to api spec
teemukataja Feb 23, 2022
5c08690
add templates endpoint request to integration tests
teemukataja Feb 23, 2022
80479a7
fix function docstring
teemukataja Feb 23, 2022
8ac6604
check folder ownership when posting an object
teemukataja Feb 24, 2022
48a927f
refactor templates to be listed under projects using old object assig…
teemukataja Feb 28, 2022
829f5a1
remove projectid requirement from template deletion
teemukataja Feb 28, 2022
1a20918
Merge branch 'develop' into feature/project-ownership
teemukataja Feb 28, 2022
48cdb68
remove deprecated user patch
teemukataja Mar 1, 2022
21f69af
fix separate template list updating in project-collection
teemukataja Mar 1, 2022
b743891
add new word
teemukataja Mar 1, 2022
02a7913
remove project check, already done in aai, unreachable code
teemukataja Mar 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/config/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ primaryid
probeset
processedreads
processingtype
projectId
projectNumber
promethion
proteinclusters
protfam
Expand Down Expand Up @@ -533,6 +535,7 @@ schemeuri
scientificname
sda
sdev
sdSubmitProjects
se
secondaryid
sectionname
Expand Down Expand Up @@ -589,6 +592,7 @@ taxonomicreferenceset
taxonomysystem
taxonomysystemversion
telephonenumber
templateId
tigrinya
tls
toctree
Expand Down
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- enum are sorted alphabetically, with the exception of other and unspecified values which are left at the end of the list
- allow for accession key in `referenceAlignment` & `process sequence` as array, previously all accession keys were converted to `accessionId` which is not correct
- add default `gender` as `unknown`

- Project ownership #346
- added new collection `project`
- added new key `projects` to `user`
- added new key `projectId` to `folder` and `template-*` collections
- new mandatory `/userinfo` value from AAI at login time `sdSubmitProjects`
- user is redirected to an info page by AAI if key is missing
- new mandatory query parameter `projectId` in `GET /folders`
- new mandatory JSON key `projectId` in `POST /folders` and `POST /templates`
- new endpoint `GET /templates` to replace `GET /users/current` `{"templates":[...]}`
- new JSON keys `index` and `tags` to `PATCH /templates/schema/templateId`, same values as were previously used in `PATCH /user` which is now removed
- WARNING: breaking change that requires fresh database, because "project" is new information that did not exist before, and it can't be migrated to existing user-owned hierarchy

### Changed

Expand Down Expand Up @@ -68,6 +78,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed

- Removed `Authlib` dependency #315
- Project ownership #346
- deprecated `folders` and `templates` keys from `GET /users/current`
- as a side effect, deprecated `items` query parameter from the same endpoint
- deprecated `PATCH /user`

### Deprecated

Expand Down
99 changes: 71 additions & 28 deletions docs/specification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,11 @@ paths:
- Submission
summary: Submit data to a specific schema
parameters:
- in: query
name: folder
schema:
type: string
description: The folder ID where object belongs to.
- name: schema
in: path
description: Name of the Metadata schema.
Expand Down Expand Up @@ -765,6 +770,37 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/403Forbidden"
/templates:
get:
tags:
- Query
summary: Get templates from selected project
parameters:
- name: projectId
in: query
description: project internal ID
schema:
type: string
required: true
responses:
200:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Templates"
401:
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/401Unauthorized"
403:
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/403Forbidden"
/templates/{schema}:
post:
tags:
Expand Down Expand Up @@ -899,6 +935,12 @@ paths:
- Query
summary: List of folders available for the user.
parameters:
- in: query
name: projectId
schema:
type: string
description: ID of the project the folder belongs to
required: true
- in: query
name: page
schema:
Expand Down Expand Up @@ -968,11 +1010,14 @@ paths:
required:
- name
- description
- projectId
properties:
name:
type: string
description:
type: string
projectId:
type: string
responses:
201:
description: OK
Expand Down Expand Up @@ -1167,11 +1212,6 @@ paths:
schema:
type: string
description: Results per page
- in: query
name: items
schema:
type: string
description: Item type name
responses:
200:
description: OK
Expand Down Expand Up @@ -1344,6 +1384,20 @@ components:
type: string
description: URL pointing to the schema source
example: https://github.com/enasequence/schema/blob/master/src/main/resources/uk/ac/ebi/ena/sra/schema/SRA.sample.xsd
Templates:
type: array
items:
type: object
properties:
accessionId:
type: string
description: internal ID of template
displayTitle:
type: string
description: name of template to be displayed in UI
schema:
type: string
description: database collection name template belongs to
Object:
type: object
required:
Expand Down Expand Up @@ -1452,6 +1506,7 @@ components:
type: object
required:
- folderId
- projectId
- name
- description
- published
Expand All @@ -1464,6 +1519,9 @@ components:
folderId:
type: string
description: Folder id
projectId:
type: string
description: Project ID this folder belongs to
name:
type: string
description: Folder name
Expand Down Expand Up @@ -1631,8 +1689,7 @@ components:
required:
- userId
- name
- drafts
- folders
- projects
additionalProperties: false
properties:
userId:
Expand All @@ -1641,35 +1698,21 @@ components:
name:
type: string
description: User's Name
drafts:
projects:
type: array
items:
type: object
required:
- accessionId
- schema
- projectId
- projectNumber
additionalProperties: false
properties:
accessionId:
projectId:
type: string
description: Accession id generated to identify an object
schema:
description: Internal accession ID for project
projectNumber:
type: string
description: type of schema this Accession ID relates to and was added in submit
tags:
type: object
description: Different tags to describe the object.
additionalProperties: true
properties:
submissionType:
type: string
description: Type of submission
enum: ["XML", "Form"]
folders:
type: array
items:
type: string
description: Folder Id
description: Human friendly project number received from AAI
UserUpdated:
type: object
required:
Expand Down
76 changes: 59 additions & 17 deletions metadata_backend/api/auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Handle Access for request and OIDC workflow."""

import hashlib
from typing import Dict, Tuple
from typing import Dict, Union, List

import ujson
from aiohttp import web
Expand All @@ -11,7 +11,7 @@

from ..helpers.logger import LOG
from .middlewares import decrypt_cookie, generate_cookie
from .operators import UserOperator
from .operators import UserOperator, ProjectOperator


class AccessHandler:
Expand Down Expand Up @@ -149,29 +149,30 @@ async def callback(self, req: Request) -> Response:
req.app["Session"][session_id] = {"oidc_state": params["state"], "access_token": session["token"]}
req.app["Cookies"].add(session_id)

user_data: Tuple[str, str]
# User data is read from AAI /userinfo and is used to create the user model in database
user_data = {
"user_id": "",
"real_name": f"{session['userinfo']['given_name']} {session['userinfo']['family_name']}",
# projects come from AAI in this form: "project1 project2 project3"
# if user is not affiliated to any projects the `sdSubmitProjects` key will be missing
"projects": session["userinfo"]["sdSubmitProjects"].split(" "),
}
if "CSCUserName" in session["userinfo"]:
user_data = (
session["userinfo"]["CSCUserName"],
f"{session['userinfo']['given_name']} {session['userinfo']['family_name']}",
)
if "remoteUserIdentifier" in session["userinfo"]:
user_data = (
session["userinfo"]["remoteUserIdentifier"],
f"{session['userinfo']['given_name']} {session['userinfo']['family_name']}",
)
user_data["user_id"] = session["userinfo"]["CSCUserName"]
elif "remoteUserIdentifier" in session["userinfo"]:
user_data["user_id"] = session["userinfo"]["remoteUserIdentifier"]
elif "sub" in session["userinfo"]:
user_data = (
session["userinfo"]["sub"],
f"{session['userinfo']['given_name']} {session['userinfo']['family_name']}",
)
user_data["user_id"] = session["userinfo"]["sub"]
else:
LOG.error(
"User was authenticated, but they are missing mandatory claim CSCUserName, remoteUserIdentifier or sub."
)
raise web.HTTPBadRequest(
reason="Could not set user, missing claim CSCUserName, remoteUserIdentifier or sub."
)

# Process project external IDs into the database and return accession IDs back to user_data
user_data["projects"] = await self._process_projects(req, user_data["projects"])
await self._set_user(req, session_id, user_data)

# done like this otherwise it will not redirect properly
Expand Down Expand Up @@ -208,7 +209,33 @@ async def logout(self, req: Request) -> Response:

raise response

async def _set_user(self, req: Request, session_id: str, user_data: Tuple[str, str]) -> None:
async def _process_projects(self, req: Request, projects: List[str]) -> List[Dict[str, str]]:
"""Process project external IDs to internal accession IDs by getting IDs\
from database and creating projects that are missing.

:raises: HTTPBadRequest in failed to add project to database
:param req: A HTTP request instance
:param projects: A list of project external IDs
:returns: A list of objects containing project accession IDs and project numbers
"""
projects.sort() # sort project numbers to be increasing in order
new_project_ids: List[Dict[str, str]] = []

db_client = req.app["db_client"]
operator = ProjectOperator(db_client)
for project in projects:
project_id = await operator.create_project(project)
project_data = {
"projectId": project_id, # internal ID
"projectNumber": project, # human friendly
}
new_project_ids.append(project_data)

return new_project_ids

async def _set_user(
self, req: Request, session_id: str, user_data: Dict[str, Union[List[Dict[str, str]], str]]
) -> None:
"""Set user in current session and return user id based on result of create_user.

:raises: HTTPBadRequest in could not get user info from AAI OIDC
Expand All @@ -219,5 +246,20 @@ async def _set_user(self, req: Request, session_id: str, user_data: Tuple[str, s

db_client = req.app["db_client"]
operator = UserOperator(db_client)

# Create user
user_id = await operator.create_user(user_data)

# Check if user's projects have changed
old_user = await operator.read_user(user_id)
if old_user["projects"] != user_data["projects"]:
update_operation = [
{
"op": "replace",
"path": "/projects",
"value": user_data["projects"],
}
]
user_id = await operator.update_user(user_id, update_operation)

req.app["Session"][session_id]["user_info"] = user_id
Loading