Skip to content

Commit e1fab33

Browse files
committed
add flag for v1 compatibility
1 parent 99e4414 commit e1fab33

File tree

2 files changed

+31
-13
lines changed

2 files changed

+31
-13
lines changed

server/mergin/sync/permissions.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,21 @@ def require_project(ws, project_name, permission) -> Project:
210210
return project
211211

212212

213-
def require_project_by_uuid(uuid: str, permission: ProjectPermissions, scheduled=False):
213+
def require_project_by_uuid(
214+
uuid: str, permission: ProjectPermissions, scheduled=False, expose=True
215+
) -> Project:
216+
"""
217+
Retrieves a project by UUID after validating existence, workspace status, and permissions.
218+
219+
Args:
220+
uuid (str): The unique identifier of the project.
221+
permission (ProjectPermissions): The permission level required to access the project.
222+
scheduled (bool, optional): If ``True``, bypasses the check for projects marked for deletion.
223+
expose (bool, optional): Controls security disclosure behavior on permission failure.
224+
- If `True`: Returns 403 Forbidden (reveals project exists but access is denied).
225+
- If `False`: Returns 404 Not Found (hides project existence for security).
226+
Defaults to `True` for v1 endpoints compatibility.
227+
"""
214228
if not is_valid_uuid(uuid):
215229
abort(404)
216230

@@ -220,16 +234,18 @@ def require_project_by_uuid(uuid: str, permission: ProjectPermissions, scheduled
220234
if not scheduled:
221235
project = project.filter(Project.removed_at.is_(None))
222236
project = project.first_or_404()
223-
# we don't want to tell anonymous user if a private project exists
224-
if current_user.is_anonymous and not project.public:
225-
abort(404)
237+
226238
workspace = project.workspace
227239
if not workspace:
228240
abort(404)
229241
if not is_active_workspace(workspace):
230242
abort(404, "Workspace doesn't exist")
231243
if not permission.check(project, current_user):
232-
abort(403, "You do not have permissions for this project")
244+
# we don't want to tell anonymous user if a private project exists
245+
if expose:
246+
abort(403, "You do not have permissions for this project")
247+
else:
248+
abort(404)
233249
return project
234250

235251

server/mergin/sync/public_api_v2_controller.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def schedule_delete_project(id):
5757
Celery job `mergin.sync.tasks.remove_projects_backups` does the
5858
rest.
5959
"""
60-
project = require_project_by_uuid(id, ProjectPermissions.Delete)
60+
project = require_project_by_uuid(id, ProjectPermissions.Delete, expose=False)
6161
project.removed_at = datetime.utcnow()
6262
project.removed_by = current_user.id
6363
db.session.commit()
@@ -68,7 +68,9 @@ def schedule_delete_project(id):
6868
@auth_required
6969
def delete_project_now(id):
7070
"""Delete the project immediately"""
71-
project = require_project_by_uuid(id, ProjectPermissions.Delete, scheduled=True)
71+
project = require_project_by_uuid(
72+
id, ProjectPermissions.Delete, scheduled=True, expose=False
73+
)
7274
project.delete()
7375

7476
return NoContent, 204
@@ -77,7 +79,7 @@ def delete_project_now(id):
7779
@auth_required
7880
def update_project(id):
7981
"""Rename project"""
80-
project = require_project_by_uuid(id, ProjectPermissions.Update)
82+
project = require_project_by_uuid(id, ProjectPermissions.Update, expose=False)
8183
new_name = request.json["name"].strip()
8284
validation_error = project_name_validation(new_name)
8385
if validation_error:
@@ -100,7 +102,7 @@ def update_project(id):
100102
@auth_required
101103
def get_project_collaborators(id):
102104
"""Get project collaborators, with both direct role and inherited role from workspace"""
103-
project = require_project_by_uuid(id, ProjectPermissions.Update)
105+
project = require_project_by_uuid(id, ProjectPermissions.Update, expose=False)
104106
project_members = []
105107
for user, workspace_role in project.workspace.members():
106108
project_role = project.get_role(user.id)
@@ -123,7 +125,7 @@ def get_project_collaborators(id):
123125
@auth_required
124126
def add_project_collaborator(id):
125127
"""Add project collaborator"""
126-
project = require_project_by_uuid(id, ProjectPermissions.Update)
128+
project = require_project_by_uuid(id, ProjectPermissions.Update, expose=False)
127129
user = User.get_by_login(request.json["user"])
128130
if not user:
129131
abort(404)
@@ -140,7 +142,7 @@ def add_project_collaborator(id):
140142
@auth_required
141143
def update_project_collaborator(id, user_id):
142144
"""Update project collaborator"""
143-
project = require_project_by_uuid(id, ProjectPermissions.Update)
145+
project = require_project_by_uuid(id, ProjectPermissions.Update, expose=False)
144146
user = User.query.filter_by(id=user_id, active=True).first_or_404()
145147
if not project.get_role(user_id):
146148
abort(404)
@@ -154,7 +156,7 @@ def update_project_collaborator(id, user_id):
154156
@auth_required
155157
def remove_project_collaborator(id, user_id):
156158
"""Remove project collaborator"""
157-
project = require_project_by_uuid(id, ProjectPermissions.Update)
159+
project = require_project_by_uuid(id, ProjectPermissions.Update, expose=False)
158160
if not project.get_role(user_id):
159161
abort(404)
160162

@@ -165,7 +167,7 @@ def remove_project_collaborator(id, user_id):
165167

166168
def get_project(id, files_at_version=None):
167169
"""Get project info. Include list of files at specific version if requested."""
168-
project = require_project_by_uuid(id, ProjectPermissions.Read)
170+
project = require_project_by_uuid(id, ProjectPermissions.Read, expose=False)
169171
data = ProjectSchemaV2().dump(project)
170172
if files_at_version:
171173
pv = ProjectVersion.query.filter_by(

0 commit comments

Comments
 (0)