-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #805 from eisbuk/emergency-disable-rules
Disable firestore rules as a workaround fix for 804
- Loading branch information
Showing
1 changed file
with
2 additions
and
198 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,204 +1,8 @@ | ||
rules_version = '2'; | ||
service cloud.firestore { | ||
match /databases/{database}/documents { | ||
// #region auth-checks | ||
function isAdminByEmail(request, org) { | ||
return ( | ||
request.auth.token.email != null && | ||
request.auth.token.email in org.data.admins | ||
); | ||
match /{document=**} { | ||
allow read, write: if true; | ||
} | ||
function isAdminByPhone(request, org) { | ||
return ( | ||
request.auth.token.phone_number != null && | ||
request.auth.token.phone_number in org.data.admins | ||
); | ||
} | ||
function isAdmin(organization) { | ||
// Given an organization name retrieve it from firestore | ||
// and check if the currently logged in user is part of the admin | ||
// array in that document. If they are return true. | ||
let org = get(/databases/$(database)/documents/organizations/$(organization)); | ||
return request.auth != null && (isAdminByEmail(request, org) || isAdminByPhone(request, org)); | ||
} | ||
// #endregion auth-checks | ||
|
||
// #region enums | ||
function getCategories() { | ||
return ["course-adults", "pre-competitive-adults", "course-minors", "pre-competitive-minors", "competitive"] | ||
} | ||
function getSlotTypes() { | ||
return ["ice", "off-ice"] | ||
} | ||
// #endregion enums | ||
|
||
// #region type-checks | ||
function checkRequiredString(input) { | ||
return ( | ||
input is string && | ||
input.size() > 0 | ||
) | ||
} | ||
function checkOptionalBoolean(data, property) { | ||
return ( | ||
!(property in data) || | ||
data[property] is bool | ||
) | ||
} | ||
// #endregion type-checks | ||
|
||
// #region regex-checks | ||
// we're using this func to check if the required date is a valid ISO string | ||
function checkValidDate(date) { | ||
return ( | ||
date is string && | ||
date.matches('[12][0-9][0-9][0-9]-[01][0-9]-[0123][0-9]') | ||
); | ||
} | ||
// we're using this func to check if the optional date is a valid ISO string | ||
function checkOptionalValidDate(data, property) { | ||
// if the field is an empty string, we bypass the check | ||
return ( | ||
!(property in data) || | ||
data[property] == "" || | ||
checkValidDate(data[property]) | ||
) | ||
} | ||
function checkValidEmail(email) { | ||
return email.matches("^\\w+([\\.#$%&*+/=?^`.{|}~-]?\\w+)*@\\w+([\\.#$%&*+/=?^`.{|}~-]?\\w+)*(\\.\\w\\w+)+$") | ||
} | ||
function checkValidPhoneNumber(phone) { | ||
return phone.matches('(\\+|00)[0-9]{9,15}') | ||
} | ||
// #endregion regex-checks | ||
|
||
// #region helpers | ||
// Check that the date isn't updated or if updated | ||
// should be the same as in the existing resource | ||
function checkConsistentDate(data, storedData) { | ||
return ( | ||
!("date" in data) || | ||
data.date == storedData.date | ||
) | ||
} | ||
// #endregion helpers | ||
|
||
match /organizations/{organization} { | ||
allow read, write: if isAdmin(organization); | ||
|
||
match /slots/{slotId} { | ||
// Only admins can read/delete slots | ||
allow read, delete: if isAdmin(organization) | ||
// Validate data integrity for create/update | ||
allow create, update: if isAdmin(organization) && | ||
// check date | ||
checkValidDate(request.resource.data.date) && | ||
// check valid type | ||
request.resource.data.type in getSlotTypes() && | ||
// contains only supported categories | ||
request.resource.data.categories.hasOnly(getCategories()) | ||
} | ||
// #endregion slot-checks | ||
|
||
// #region slots-by-day-checks | ||
match /slotsByDay/{document=**} { | ||
// Everyone has access to all available slots. | ||
// Security-wise this is equivalent to showing a form that lets unauthenticated | ||
// users book their slots, as is the current solution. | ||
allow read: if true; | ||
} | ||
// #endregion slots-by-day-checks | ||
|
||
// #endregion bookings-checks | ||
match /bookings/{secretKey} { | ||
// Anybody with 'secretKey' can read from 'bookings' | ||
// No one should be able to write to bookings document (it's done through cloud functions) | ||
allow read: if true; | ||
|
||
function checkBookingSlot(slotId, bookingData) { | ||
let bookedSlot = get(/databases/$(database)/documents/organizations/$(organization)/slots/$(slotId)); | ||
|
||
return ( | ||
// Check date integrity | ||
bookingData.date == bookedSlot.data.date && | ||
// Check interval integrity | ||
bookingData.interval in bookedSlot.data.intervals | ||
); | ||
} | ||
function checkBookedSlotCategory(slotId) { | ||
let customer = get(/databases/$(database)/documents/organizations/$(organization)/bookings/$(secretKey)); | ||
let bookedSlot = get(/databases/$(database)/documents/organizations/$(organization)/slots/$(slotId)); | ||
|
||
return customer.data.categories.hasAny(bookedSlot.data.categories) | ||
} | ||
match /bookedSlots/{slotId} { | ||
// Anybody with secret key should be able to read and write (with valid data) to 'bookedSlots' | ||
allow read, delete: if true; | ||
// Check data integrity for create/update | ||
allow create, update: if ( | ||
checkBookingSlot(slotId, request.resource.data) && | ||
checkBookedSlotCategory(slotId) | ||
) | ||
} | ||
match /attendedSlots/{slotId} { | ||
allow read: if true; | ||
} | ||
match /calendar/{date} { | ||
// Anybody with secret key should be able to read and write | ||
allow read, write: if true; | ||
|
||
} | ||
} | ||
// #endregion bookings-checks | ||
|
||
// #region customer-checks | ||
match /customers/{customerId} { | ||
// Only admins can access customer collection | ||
allow read, delete: if isAdmin(organization) | ||
// Check data integrity | ||
allow create, update: if ( | ||
isAdmin(organization) && | ||
// Check required fields | ||
checkRequiredString(request.resource.data.name) && | ||
checkRequiredString(request.resource.data.surname) && | ||
// Check optional dates | ||
checkOptionalValidDate(request.resource.data, "birthday") && | ||
checkOptionalValidDate(request.resource.data, "certificateExpiration") && | ||
request.resource.data.categories.hasOnly(getCategories()) && | ||
// Check valid email | ||
( | ||
!("email" in request.resource.data) || | ||
request.resource.data.email == "" || | ||
checkValidEmail(request.resource.data.email) | ||
) && | ||
// Check valid phone number | ||
( | ||
!("phone" in request.resource.data) || | ||
request.resource.data.phone == "" || | ||
checkValidPhoneNumber(request.resource.data.phone) | ||
) | ||
) | ||
} | ||
// #region customer-checks | ||
|
||
// #region attendance-checks | ||
match /attendance/{slotId} { | ||
// Only admin can access attendance | ||
// Only read and (conditional) update access are allowed | ||
// Everything else gets updated by cloud function | ||
allow read: if isAdmin(organization); | ||
allow update: if ( | ||
isAdmin(organization) && | ||
// Check that the date doesn't change | ||
checkConsistentDate(request.resource.data, resource.data) | ||
) | ||
} | ||
// #endregion attendance-checks | ||
} | ||
// #region public-info-check | ||
match /publicOrgInfo/{organization} { | ||
allow read: if true; | ||
} | ||
// #endregion public-info-check | ||
} | ||
} |