241 lines
8.3 KiB
Plaintext
241 lines
8.3 KiB
Plaintext
rules_version = '2';
|
|
|
|
service cloud.firestore {
|
|
match /databases/{database}/documents {
|
|
function isSignedIn() {
|
|
return request.auth != null;
|
|
}
|
|
|
|
function isAdmin() {
|
|
return request.auth.token.admin == true;
|
|
}
|
|
|
|
function getGroupRole(groupId) {
|
|
return get(/databases/$(database)/documents/users/$(request.auth.uid)/groups/$(groupId)).data.role;
|
|
}
|
|
|
|
function isSetOwner(setId) {
|
|
return get(/databases/$(database)/documents/sets/$(setId)).data.owner == request.auth.uid;
|
|
}
|
|
|
|
function isSetOwnerAndHasNoGroups(setId) {
|
|
let data = get(/databases/$(database)/documents/sets/$(setId)).data;
|
|
return data.owner == request.auth.uid && (data == null || !("groups" in data) || data.groups == []);
|
|
}
|
|
|
|
function isSetOwnerOrIsPublic(setId) {
|
|
let data = get(/databases/$(database)/documents/sets/$(setId)).data;
|
|
return data.public || data.owner == request.auth.uid;
|
|
}
|
|
|
|
function verifyCreateFields(fields) {
|
|
return request.resource.data.keys().hasAll(fields[0]) && request.resource.data.keys().hasOnly(fields[1]);
|
|
}
|
|
|
|
function verifyUpdateFields(fields) {
|
|
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
|
|
return affectedKeys.hasAll([]) && affectedKeys.hasOnly(fields[1]);
|
|
}
|
|
|
|
function getRequestField(field, default_value) {
|
|
return request.resource.data.get(field, default_value);
|
|
}
|
|
|
|
function verifyBoolType(field) {
|
|
return getRequestField(field, true) is bool;
|
|
}
|
|
|
|
function verifyStringType(field) {
|
|
return getRequestField(field, "") is string;
|
|
}
|
|
|
|
function verifyEmptyArrayType(field) {
|
|
return getRequestField(field, []) == [];
|
|
}
|
|
|
|
match /users/{userId} {
|
|
function isSignedInUser() {
|
|
return request.auth.uid == userId;
|
|
}
|
|
|
|
function verifyThemeValue() {
|
|
let requestField = getRequestField("theme", "default");
|
|
let themes = ["default", "red", "maroon", "green", "light-blue", "pink", "yellow", "orange"];
|
|
return requestField in themes;
|
|
}
|
|
|
|
function verifyFieldTypes() {
|
|
return verifyBoolType("sound") &&
|
|
verifyBoolType("coloredEdges") &&
|
|
verifyThemeValue();
|
|
}
|
|
|
|
function getPossibleFields() {
|
|
let requiredFields = ["sound", "theme", "coloredEdges"];
|
|
let optionalFields = [];
|
|
let allFields = requiredFields.concat(optionalFields);
|
|
return [requiredFields, allFields];
|
|
}
|
|
|
|
allow read: if isSignedIn() && isSignedInUser(); // is current user's data
|
|
allow update: if isSignedIn() && isSignedInUser() && verifyUpdateFields(getPossibleFields()) && verifyFieldTypes();
|
|
|
|
match /groups/{groupId} {
|
|
function verifyGroupFieldTypes() {
|
|
return getRequestField("role", "member") == "member" ||
|
|
getRequestField("role", "contributor") == "contributor" ||
|
|
getRequestField("role", "owner") == "owner";
|
|
}
|
|
|
|
function getPossibleGroupFields() {
|
|
let requiredFields = ["role"];
|
|
let optionalFields = [];
|
|
let allFields = requiredFields.concat(optionalFields);
|
|
return [requiredFields, allFields];
|
|
}
|
|
|
|
allow read: if isSignedIn() && (isSignedInUser() || getGroupRole(groupId) == "owner" || isAdmin()); // is current user's data or is owner of group or is admin
|
|
allow delete: if isSignedIn() && ((isSignedInUser() && getGroupRole(groupId) != "owner") || (!isSignedInUser() && getGroupRole(groupId) == "owner") || isAdmin())
|
|
allow create: if isSignedIn() && isSignedInUser() && (getRequestField("role", "") == "member" || (isAdmin() && verifyGroupFieldTypes())) && verifyCreateFields(getPossibleGroupFields());
|
|
allow update: if isSignedIn() &&
|
|
(getGroupRole(groupId) == "owner" || isAdmin()) &&
|
|
verifyGroupFieldTypes() &&
|
|
!(isSignedInUser()) && verifyUpdateFields(getPossibleGroupFields());
|
|
allow update: if isSignedIn() &&
|
|
isAdmin() &&
|
|
isSignedInUser() &&
|
|
getRequestField("role", "") == "owner";
|
|
}
|
|
}
|
|
|
|
match /groups/{groupId} {
|
|
function verifyFieldTypes() {
|
|
return verifyStringType("display_name");
|
|
}
|
|
|
|
function getPossibleFields() {
|
|
let nonStaticFields = ["display_name"];
|
|
let staticFields = ["join_code", "sets", "users"];
|
|
|
|
let allFields = staticFields.concat(nonStaticFields);
|
|
return [nonStaticFields, allFields];
|
|
}
|
|
|
|
function getPossibleUpdateFields() {
|
|
let fields = getPossibleFields();
|
|
return [[], fields[0]];
|
|
}
|
|
|
|
allow read: if isSignedIn() && (getGroupRole(groupId) != null || isAdmin());
|
|
allow update: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()) && verifyUpdateFields(getPossibleUpdateFields()) && verifyFieldTypes();
|
|
allow delete: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin());
|
|
}
|
|
|
|
|
|
match /sets/{setId} {
|
|
function verifyPublicField() {
|
|
return ((resource == null || resource.data == null || resource.data.groups == [] || resource.data.group == null) && verifyBoolType("public")) ||
|
|
(resource.data.groups != [] && resource.data.groups is list && getRequestField("public", true) == true);
|
|
}
|
|
|
|
function verifyFieldTypes() {
|
|
return verifyPublicField() &&
|
|
verifyStringType("title") &&
|
|
verifyStringType("owner") &&
|
|
verifyEmptyArrayType("groups");
|
|
}
|
|
|
|
function getPossibleFields() {
|
|
let nonStaticFields = ["public", "title"];
|
|
let staticFields = ["owner", "groups"];
|
|
|
|
let allFields = staticFields.concat(nonStaticFields);
|
|
return [nonStaticFields, allFields];
|
|
}
|
|
|
|
function getPossibleCreateFields() {
|
|
let fields = getPossibleFields();
|
|
return [fields[1], fields[1]];
|
|
}
|
|
|
|
function getPossibleUpdateFields() {
|
|
let fields = getPossibleFields();
|
|
return [[], fields[0]];
|
|
}
|
|
|
|
allow read: if isSignedIn() && (request.auth.uid == resource.data.owner || resource.data.public == true);
|
|
allow create: if isSignedIn() && request.auth.uid == request.resource.data.owner && verifyCreateFields(getPossibleCreateFields()) && verifyFieldTypes();
|
|
allow update: if isSignedIn() && request.auth.uid == resource.data.owner && verifyUpdateFields(getPossibleUpdateFields()) && verifyFieldTypes();
|
|
allow delete: if isSignedIn() && request.auth.uid == resource.data.owner && (resource == null || resource.data == null || resource.data.groups == []);
|
|
|
|
match /vocab/{vocabId} {
|
|
function verifyVocabFieldTypes() {
|
|
return verifyStringType("term") &&
|
|
verifyStringType("definition") &&
|
|
verifyBoolType("sound");
|
|
}
|
|
|
|
function getPossibleFields() {
|
|
let nonStaticFields = ["term", "definition"];
|
|
let staticFields = ["sound"];
|
|
|
|
let allFields = staticFields.concat(nonStaticFields);
|
|
return [nonStaticFields, allFields];
|
|
}
|
|
|
|
function getPossibleCreateFields() {
|
|
let fields = getPossibleFields();
|
|
return [fields[1], fields[1]];
|
|
}
|
|
|
|
function getPossibleUpdateFields() {
|
|
let fields = getPossibleFields();
|
|
return [[], fields[0]];
|
|
}
|
|
|
|
allow read: if isSignedIn() && isSetOwnerOrIsPublic(setId);
|
|
allow create: if isSignedIn() && isSetOwner(setId) && verifyCreateFields(getPossibleCreateFields()) && verifyVocabFieldTypes();
|
|
allow update: if isSignedIn() && isSetOwner(setId) && verifyUpdateFields(getPossibleUpdateFields()) && verifyVocabFieldTypes();
|
|
allow delete: if isSignedIn() && isSetOwnerAndHasNoGroups(setId);
|
|
}
|
|
}
|
|
|
|
match /progress/{progressId} {
|
|
function isProgressUser() {
|
|
return get(/databases/$(database)/documents/progress/$(progressId)).data.uid == request.auth.uid;
|
|
}
|
|
|
|
function isLanguageSwitched() {
|
|
return get(/databases/$(database)/documents/progress/$(progressId)).data.switch_language;
|
|
}
|
|
|
|
function isNotComplete() {
|
|
return get(/databases/$(database)/documents/progress/$(progressId)).data.progress < get(/databases/$(database)/documents/progress/$(progressId)).data.questions.size();
|
|
}
|
|
|
|
allow read: if isSignedIn() && resource.data.uid == request.auth.uid;
|
|
allow delete: if isSignedIn() && resource.data.uid == request.auth.uid && isNotComplete();
|
|
|
|
match /terms/{vocabId} {
|
|
allow read: if isSignedIn() && isProgressUser() && (!(isLanguageSwitched()) || !(isNotComplete()));
|
|
}
|
|
|
|
match /definitions/{vocabId} {
|
|
allow read: if isSignedIn() && isProgressUser() && (isLanguageSwitched() || !(isNotComplete()));
|
|
}
|
|
}
|
|
|
|
match /join_codes/{joinCode} {
|
|
allow get: if isSignedIn();
|
|
}
|
|
|
|
match /completed_progress/{setIds} {
|
|
allow get: if isSignedIn();
|
|
}
|
|
|
|
match /incorrect_answers/{incorrectAnswerId} {
|
|
allow read: if isSignedIn();
|
|
}
|
|
}
|
|
}
|