-
Notifications
You must be signed in to change notification settings - Fork 2
/
firestore.rules
252 lines (210 loc) · 11.2 KB
/
firestore.rules
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
rules_version = '2';
// Read rules can be divided into get and list rules
// Write rules can be divided into create, update, and delete rules
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write : if request.auth.token.get("role", null) == "admin";
}
function isSignedIn() {
return request.auth != null;
}
function isVerified() {
return isSignedIn() && request.auth.token.email_verified;
}
match /users/{uid} {
function isOwner() {
return isVerified() && request.auth.uid == uid;
}
function validateJoinedAssociations() {
// Prevent the user from changing the joinedAssociations field
// This will be handled by a Firebase Function
return request.resource.data.joinedAssociations == resource.data.joinedAssociations;
}
function onlySavedEventsUpdated() {
// Check that the only updated field is the numberOfSaved field
// and that it was only incremented or decremented by 1
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
let onlySavedEventsChanged = affectedKeys.hasOnly(['savedEvents']);
return onlySavedEventsChanged;
}
function onlyFollowedAssociationsUpdated() {
// Check that the only updated field is the numberOfSaved field
// and that it was only incremented or decremented by 1
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
let onlyFollowedAssociationsChanged = affectedKeys.hasOnly(['followedAssociations']);
return onlyFollowedAssociationsChanged;
}
function validate() {
// Check that the user document has the correct fields
let fields = ['uid', 'email', 'firstName', 'lastName', 'biography', 'savedEvents', 'followedAssociations', 'joinedAssociations', 'interests', 'socials', 'profilePicture'];
let hasCorrectNumberOfFields = request.resource.data.size() == fields.size();
let hasCorrectFields = request.resource.data.keys().hasAll(fields);
return hasCorrectNumberOfFields && hasCorrectFields &&
request.resource.data.uid == uid &&
request.resource.data.email == request.auth.token.email &&
request.resource.data.firstName is string &&
request.resource.data.firstName.size() <= 30 &&
request.resource.data.lastName is string &&
request.resource.data.lastName.size() <= 30 &&
request.resource.data.biography is string &&
request.resource.data.biography.size() <= 300 &&
request.resource.data.savedEvents is list &&
request.resource.data.followedAssociations is list &&
request.resource.data.joinedAssociations is list &&
request.resource.data.interests is list &&
request.resource.data.socials is list &&
request.resource.data.profilePicture is string;
}
allow get: if isVerified();
allow list: if isVerified();
allow delete: if isOwner();
allow create: if isOwner() && validate();
allow update: if validateJoinedAssociations() && validate() && isOwner();
}
match /associations/{uid} {
function isMember() {
let members = get(/databases/$(database)/documents/associations/$(uid)).data.get("members", null);
return (members != null) && members.keys().hasAny([request.auth.uid]);
}
function onlyUpdatedFollowerCount() {
// Check that the only updated field is the followersCount field
// and that it was only incremented or decremented by 1
let newCount = request.resource.data.get("followersCount", null);
let oldCount = resource.data.get("followersCount", null);
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
let onlyFollowerCountChanged = affectedKeys.hasOnly(['followersCount']);
return onlyFollowerCountChanged && (newCount == oldCount || newCount == oldCount + 1 || newCount == oldCount - 1);
}
function validate() {
// Check that the association document has the correct fields
let fields = ['uid', 'url', 'name', 'fullName', 'category', 'description', 'followersCount', 'members','roles', 'image', 'events', "principalEmailAddress"];
let hasCorrectNumberOfFields = request.resource.data.size() == fields.size();
let hasCorrectFields = request.resource.data.keys().hasAll(fields);
return hasCorrectNumberOfFields && hasCorrectFields &&
request.resource.data.uid == uid &&
request.resource.data.url is string &&
request.resource.data.name is string &&
request.resource.data.fullName is string &&
request.resource.data.category is string &&
request.resource.data.description is string &&
request.resource.data.followersCount is int &&
request.resource.data.followersCount >= 0 &&
request.resource.data.members is map &&
request.resource.data.roles is map &&
request.resource.data.image is string &&
request.resource.data.events is list &&
request.resource.data.principalEmailAddress is string;
}
allow read: if isVerified();
// Allow update if the user is a member of the association or if the changed data is the followersCount field
allow update: if isVerified() && (isMember() || onlyUpdatedFollowerCount()) && validate();
}
match /associationsRequest/{uid} {
function validate() {
// Check that the association document has the correct fields
let fields = ['uid', 'url', 'name', 'fullName', 'category', 'description', 'followersCount', 'members','roles', 'image', 'events', "principalEmailAddress"];
let hasCorrectNumberOfFields = request.resource.data.size() == fields.size();
let hasCorrectFields = request.resource.data.keys().hasAll(fields);
return hasCorrectNumberOfFields && hasCorrectFields &&
request.resource.data.uid == uid &&
request.resource.data.url is string &&
request.resource.data.name is string &&
request.resource.data.fullName is string &&
request.resource.data.category is string &&
request.resource.data.description is string &&
request.resource.data.followersCount is int &&
request.resource.data.followersCount >= 0 &&
request.resource.data.members is map &&
request.resource.data.roles is map &&
request.resource.data.image is string &&
request.resource.data.events is list &&
request.resource.data.principalEmailAddress is string;
}
allow create: if isVerified() && validate();
}
match /events/{uid} {
function isEventOrganiser(organisers) {
// Check that the user creating the event is member of any of the organisers
return organisers.hasAny(
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("joinedAssociations", null)
);
}
function onlyUpdatedEventPictures() {
// Check that the only updated field is the eventPictures field
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
let onlyEventPicturesChanged = affectedKeys.hasOnly(['eventPictures']);
return onlyEventPicturesChanged;
}
function onlyUpdatedSavedCount() {
// Check that the only updated field is the numberOfSaved field
// and that it was only incremented or decremented by 1
let newCount = request.resource.data.get("numberOfSaved", null);
let oldCount = resource.data.get("numberOfSaved", null);
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
let onlySavedCountChanged = affectedKeys.hasOnly(["numberOfSaved"]);
return onlySavedCountChanged && newCount != null && oldCount != null;
}
function validate() {
// Check that the event document has the correct fields
let fields = ['uid', 'title', 'organisers', 'taggedAssociations', 'image', 'description', 'catchyDescription', 'price', 'startDate', 'endDate', 'location', 'types', 'maxNumberOfPlaces', 'numberOfSaved', 'eventPictures'];
let hasCorrectNumberOfFields = request.resource.data.size() == fields.size();
let hasCorrectFields = request.resource.data.keys().hasAll(fields);
return hasCorrectNumberOfFields && hasCorrectFields &&
request.resource.data.uid == uid &&
request.resource.data.title is string &&
request.resource.data.title.size() <= 30 &&
request.resource.data.organisers is list &&
request.resource.data.taggedAssociations is list &&
request.resource.data.image is string &&
request.resource.data.description is string &&
request.resource.data.description.size() <= 300 &&
request.resource.data.catchyDescription is string &&
request.resource.data.catchyDescription.size() <= 100 &&
request.resource.data.price is number &&
request.resource.data.price >= 0 &&
request.resource.data.startDate is timestamp &&
request.resource.data.endDate is timestamp &&
request.resource.data.location is map &&
request.resource.data.location.latitude is number &&
request.resource.data.location.longitude is number &&
request.resource.data.location.name is string &&
request.resource.data.types is list &&
request.resource.data.maxNumberOfPlaces is number &&
request.resource.data.maxNumberOfPlaces >= -1 &&
request.resource.data.numberOfSaved is number &&
request.resource.data.numberOfSaved >= 0 &&
request.resource.data.eventPictures is list;
}
allow read: if isVerified();
// To create an event, the user must be an organiser
// specified in the event document of the request
allow create: if isVerified() && isEventOrganiser(request.resource.data.organisers) && validate();
// To update or delete an event, the user must be an
// organiser in the existing event document
allow update: if isVerified() && (isEventOrganiser(resource.data.organisers) || onlyUpdatedSavedCount() || onlyUpdatedEventPictures()) && validate();
allow delete: if isVerified() && isEventOrganiser(resource.data.organisers);
}
match /eventUserPictures/{uid} {
function isAuthor() {
let author = resource.data.get("author", null);
return isVerified() && author != null && request.auth.uid == author;
}
function validate() {
// Check that the eventUserPicture document has the correct fields
let fields = ['uid', 'image', 'author', 'likes'];
let hasCorrectNumberOfFields = request.resource.data.size() == fields.size();
let hasCorrectFields = request.resource.data.keys().hasAll(fields);
return hasCorrectNumberOfFields && hasCorrectFields &&
request.resource.data.uid == uid &&
request.resource.data.image is string &&
request.resource.data.author is string &&
request.resource.data.likes is list;
}
allow read: if isVerified();
allow create: if isVerified() && validate();
allow update: if isVerified() && validate();
allow delete: if isAuthor();
}
}
}