-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstorage.rules
More file actions
74 lines (60 loc) · 2.34 KB
/
Copy pathstorage.rules
File metadata and controls
74 lines (60 loc) · 2.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
rules_version = '2';
/**
* Upload paths (use consistently in app code):
* projects/{projectId}/runs/{runId}/results/{caseId}/{fileName}
* (optional subfolders: .../results/{caseId}/evidence/screenshot.png — see isRunResultAttachmentPath)
*
* Testers: create/update only under run result paths. Deletes: test_lead / admin only.
* Other paths under projects/{projectId}/...: read = members; write = test_lead / admin only.
*
* Depends on Firestore: users/{uid}.role, projects/{projectId}.memberIds
*/
service firebase.storage {
match /b/{bucket}/o {
function userDoc() {
return firestore.get(/databases/(default)/documents/users/$(request.auth.uid));
}
function projectDoc(projectId) {
return firestore.get(/databases/(default)/documents/projects/$(projectId));
}
function signedIn() {
return request.auth != null;
}
function userProfileExists() {
return signedIn() &&
firestore.exists(/databases/(default)/documents/users/$(request.auth.uid));
}
function userRole() {
return userDoc().data.role;
}
function canManageQualityContent() {
return userProfileExists() && userRole() in ['admin', 'test_lead'];
}
function isProjectMember(projectId) {
return signedIn() &&
firestore.exists(/databases/(default)/documents/projects/$(projectId)) &&
request.auth.uid in projectDoc(projectId).data.memberIds;
}
function isGlobalAdmin() {
return userProfileExists() && userRole() == 'admin';
}
function canAccessProjectStorage(projectId) {
return isProjectMember(projectId) || isGlobalAdmin();
}
/** Path after projectId/ — e.g. runs/r1/results/c1/evidence/file.png */
function isRunResultAttachmentPath(allPaths) {
return allPaths.matches('^runs/[^/]+/results/[^/]+/.+');
}
/**
* Single match avoids overlap: one path must not satisfy two different allow rules incorrectly.
*/
match /projects/{projectId}/{allPaths=**} {
allow read: if canAccessProjectStorage(projectId);
allow create, update: if canAccessProjectStorage(projectId) && userProfileExists() && (
canManageQualityContent() ||
(userRole() == 'tester' && isRunResultAttachmentPath(allPaths))
);
allow delete: if canAccessProjectStorage(projectId) && canManageQualityContent();
}
}
}