Skip to content

Commit acb4d43

Browse files
Merge pull request #6968 from hotosm/develop
v5.1.0 to staging
2 parents 89eb0d9 + 41e4dc2 commit acb4d43

56 files changed

Lines changed: 1365 additions & 1100 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pr_test_backend.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
3232
- name: Run PEP8 checks
3333
run: |
34-
flake8 manage.py backend tests migrations
34+
flake8 manage.py backend tests migrations --ignore=E203,W503
3535
black --check manage.py backend tests migrations
3636
3737
pytest:

backend/api/projects/activities.py

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from typing import Optional
2+
from backend.models.dtos.user_dto import AuthUserDTO
3+
from backend.models.postgis.statuses import ProjectStatus
4+
from backend.services.users.authentication_service import login_required_optional
15
from databases import Database
26
from fastapi import APIRouter, Depends, Request, Query
3-
7+
from fastapi.responses import JSONResponse
48
from backend.db import get_db
59
from backend.services.project_service import ProjectService
610
from backend.services.stats_service import StatsService
@@ -16,6 +20,7 @@
1620
async def get_activities(
1721
project_id: int,
1822
page: int = Query(1, description="Page of results user requested", ge=1),
23+
user: Optional[AuthUserDTO] = Depends(login_required_optional),
1924
db: Database = Depends(get_db),
2025
):
2126
"""
@@ -44,14 +49,46 @@ async def get_activities(
4449
500:
4550
description: Internal Server Error
4651
"""
47-
await ProjectService.exists(project_id, db)
52+
53+
is_private, status = await ProjectService.get_project_privacy_and_status(
54+
project_id, db
55+
)
56+
# If private or draft, enforce login + permission
57+
if is_private or status == ProjectStatus.DRAFT.value:
58+
user_id = user.id if user else None
59+
if user is None:
60+
return JSONResponse(
61+
content={
62+
"Error": "User not permitted: Private Project",
63+
"SubCode": "PrivateProject",
64+
},
65+
status_code=403,
66+
)
67+
68+
project_dto = await ProjectService.get_project_dto_for_mapper(
69+
project_id,
70+
user_id,
71+
db,
72+
)
73+
if not project_dto:
74+
75+
return JSONResponse(
76+
content={
77+
"Error": "User not permitted: Private Project",
78+
"SubCode": "PrivateProject",
79+
},
80+
status_code=403,
81+
)
4882
activity = await StatsService.get_latest_activity(project_id, page, db)
4983
return activity
5084

5185

5286
@router.get("/{project_id}/activities/latest/")
5387
async def get_latest_activities(
54-
request: Request, project_id: int, db: Database = Depends(get_db)
88+
request: Request,
89+
project_id: int,
90+
user: Optional[AuthUserDTO] = Depends(login_required_optional),
91+
db: Database = Depends(get_db),
5592
):
5693
"""
5794
Get latest user activity on all of project task
@@ -74,6 +111,35 @@ async def get_latest_activities(
74111
500:
75112
description: Internal Server Error
76113
"""
77-
await ProjectService.exists(project_id, db)
114+
115+
is_private, status = await ProjectService.get_project_privacy_and_status(
116+
project_id, db
117+
)
118+
# If private or draft, enforce login + permission
119+
if is_private or status == ProjectStatus.DRAFT.value:
120+
user_id = user.id if user else None
121+
if user is None:
122+
return JSONResponse(
123+
content={
124+
"Error": "User not permitted: Private Project",
125+
"SubCode": "PrivateProject",
126+
},
127+
status_code=403,
128+
)
129+
130+
project_dto = await ProjectService.get_project_dto_for_mapper(
131+
project_id,
132+
user_id,
133+
db,
134+
)
135+
if not project_dto:
136+
137+
return JSONResponse(
138+
content={
139+
"Error": "User not permitted: Private Project",
140+
"SubCode": "PrivateProject",
141+
},
142+
status_code=403,
143+
)
78144
activity = await StatsService.get_last_activity(project_id, db)
79145
return activity

backend/api/projects/contributions.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
from typing import Optional
2+
from backend.models.dtos.user_dto import AuthUserDTO
3+
from backend.models.postgis.statuses import ProjectStatus
4+
from backend.services.users.authentication_service import login_required_optional
15
from databases import Database
26
from fastapi import APIRouter, Depends
3-
7+
from fastapi.responses import JSONResponse
48
from backend.db import get_db
5-
from backend.models.postgis.project import Project
69
from backend.services.project_service import ProjectService
710
from backend.services.stats_service import StatsService
811

@@ -14,7 +17,11 @@
1417

1518

1619
@router.get("/{project_id}/contributions/")
17-
async def get_project_contributions(project_id: int, db: Database = Depends(get_db)):
20+
async def get_project_contributions(
21+
project_id: int,
22+
user: Optional[AuthUserDTO] = Depends(login_required_optional),
23+
db: Database = Depends(get_db),
24+
):
1825
"""
1926
Get all user contributions on a project
2027
---
@@ -37,7 +44,36 @@ async def get_project_contributions(project_id: int, db: Database = Depends(get_
3744
500:
3845
description: Internal Server Error
3946
"""
40-
await Project.exists(project_id, db)
47+
48+
is_private, status = await ProjectService.get_project_privacy_and_status(
49+
project_id, db
50+
)
51+
# If private or draft, enforce login + permission
52+
if is_private or status == ProjectStatus.DRAFT.value:
53+
user_id = user.id if user else None
54+
if user is None:
55+
return JSONResponse(
56+
content={
57+
"Error": "User not permitted: Private Project",
58+
"SubCode": "PrivateProject",
59+
},
60+
status_code=403,
61+
)
62+
63+
project_dto = await ProjectService.get_project_dto_for_mapper(
64+
project_id,
65+
user_id,
66+
db,
67+
)
68+
if not project_dto:
69+
70+
return JSONResponse(
71+
content={
72+
"Error": "User not permitted: Private Project",
73+
"SubCode": "PrivateProject",
74+
},
75+
status_code=403,
76+
)
4177
contributions = await StatsService.get_user_contributions(project_id, db)
4278
return contributions
4379

backend/api/projects/resources.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
ProjectSearchDTO,
1717
)
1818
from backend.models.dtos.user_dto import AuthUserDTO
19-
from backend.models.postgis.statuses import UserRole
19+
from backend.models.postgis.statuses import ProjectStatus, UserRole
2020
from backend.services.organisation_service import OrganisationService
2121
from backend.services.project_admin_service import (
2222
InvalidData,
@@ -1104,7 +1104,10 @@ async def get_mapped_projects(
11041104

11051105
@router.get("/{project_id}/queries/summary/")
11061106
async def get_project_summary(
1107-
request: Request, project_id: int, db: Database = Depends(get_db)
1107+
request: Request,
1108+
project_id: int,
1109+
user: Optional[AuthUserDTO] = Depends(login_required_optional),
1110+
db: Database = Depends(get_db),
11081111
):
11091112
"""
11101113
Gets project summary
@@ -1135,6 +1138,37 @@ async def get_project_summary(
11351138
description: Internal Server Error
11361139
"""
11371140
preferred_locale = request.headers.get("accept-language")
1141+
1142+
is_private, status = await ProjectService.get_project_privacy_and_status(
1143+
project_id, db
1144+
)
1145+
# If private or draft, enforce login + permission
1146+
if is_private or status == ProjectStatus.DRAFT.value:
1147+
user_id = user.id if user else None
1148+
if user is None:
1149+
return JSONResponse(
1150+
content={
1151+
"Error": "User not permitted: Private Project",
1152+
"SubCode": "PrivateProject",
1153+
},
1154+
status_code=403,
1155+
)
1156+
1157+
project_dto = await ProjectService.get_project_dto_for_mapper(
1158+
project_id,
1159+
user_id,
1160+
db,
1161+
)
1162+
if not project_dto:
1163+
1164+
return JSONResponse(
1165+
content={
1166+
"Error": "User not permitted: Private Project",
1167+
"SubCode": "PrivateProject",
1168+
},
1169+
status_code=403,
1170+
)
1171+
11381172
summary = await ProjectService.get_project_summary(project_id, db, preferred_locale)
11391173
return summary
11401174

backend/api/tasks/resources.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import io
22
import json
3+
from typing import Optional
34

5+
from backend.models.dtos.user_dto import AuthUserDTO
46
from databases import Database
57
from fastapi import APIRouter, Depends, Request, Query
68
from fastapi.responses import JSONResponse, Response, StreamingResponse
@@ -9,12 +11,12 @@
911

1012
from backend.db import get_db
1113
from backend.models.dtos.grid_dto import GridDTO
12-
from backend.models.postgis.statuses import UserRole
14+
from backend.models.postgis.statuses import ProjectStatus, UserRole
1315
from backend.models.postgis.utils import InvalidGeoJson
1416
from backend.services.grid.grid_service import GridService
1517
from backend.services.mapping_service import MappingService
1618
from backend.services.project_service import ProjectService, ProjectServiceError
17-
from backend.services.users.authentication_service import tm
19+
from backend.services.users.authentication_service import login_required_optional, tm
1820
from backend.services.users.user_service import UserService
1921
from backend.services.validator_service import ValidatorService
2022

@@ -75,6 +77,7 @@ async def get_project_tasks(
7577
project_id: int,
7678
tasks: str = Query(default=None),
7779
as_file: bool = Query(default=False, alias="as_file"),
80+
user: Optional[AuthUserDTO] = Depends(login_required_optional),
7881
db: Database = Depends(get_db),
7982
):
8083
"""
@@ -112,8 +115,38 @@ async def get_project_tasks(
112115
description: Internal Server Error
113116
"""
114117
try:
115-
tasks_json = await ProjectService.get_project_tasks(db, project_id, tasks)
116118

119+
is_private, status = await ProjectService.get_project_privacy_and_status(
120+
project_id, db
121+
)
122+
# If private or draft, enforce login + permission
123+
if is_private or status == ProjectStatus.DRAFT.value:
124+
user_id = user.id if user else None
125+
if user is None:
126+
return JSONResponse(
127+
content={
128+
"Error": "User not permitted: Private Project",
129+
"SubCode": "PrivateProject",
130+
},
131+
status_code=403,
132+
)
133+
134+
project_dto = await ProjectService.get_project_dto_for_mapper(
135+
project_id,
136+
user_id,
137+
db,
138+
)
139+
if not project_dto:
140+
141+
return JSONResponse(
142+
content={
143+
"Error": "User not permitted: Private Project",
144+
"SubCode": "PrivateProject",
145+
},
146+
status_code=403,
147+
)
148+
149+
tasks_json = await ProjectService.get_project_tasks(db, project_id, tasks)
117150
if as_file:
118151
tasks_str = json.dumps(tasks_json, indent=4)
119152
return Response(

0 commit comments

Comments
 (0)