diff --git a/firestore.rules b/firestore.rules index 3dc6e8c..3e189d4 100644 --- a/firestore.rules +++ b/firestore.rules @@ -7,7 +7,7 @@ service cloud.firestore { } function isAdmin() { - return request.auth.token.admin; + return request.auth.token.admin == true; } function getGroupRole(groupId) { @@ -43,19 +43,22 @@ service cloud.firestore { return getRequestField(field, "") is string; } + function verifyNullType(field) { + return getRequestField(field, null) == null; + } + match /users/{userId} { function isSignedInUser() { return request.auth.uid == userId; } function verifyFieldTypes() { - return verifyStringType("display_name") && - verifyBoolType("sound") && + return verifyBoolType("sound") && verifyStringType("theme"); } function getPossibleFields() { - let requiredFields = ["display_name", "sound", "theme"]; + let requiredFields = ["sound", "theme"]; let optionalFields = []; let allFields = requiredFields.concat(optionalFields); return [requiredFields, allFields]; @@ -63,7 +66,6 @@ service cloud.firestore { allow read: if isSignedIn() && isSignedInUser(); // is current user's data allow update: if isSignedIn() && isSignedInUser() && verifyUpdateFields(getPossibleFields()) && verifyFieldTypes(); - allow delete: if isSignedIn() && (isSignedInUser() || isAdmin()); match /groups/{groupId} { function verifyGroupFieldTypes() { @@ -80,8 +82,8 @@ service cloud.firestore { } allow read, delete: if isSignedIn() && (isSignedInUser() || getGroupRole(groupId) == "owner" || isAdmin()); // is current user's data or is owner of group or is admin - allow create: if isSignedIn() && ((isSignedInUser() && getRequestField("role", "") == "member") || ((getGroupRole(groupId) == "owner" || isAdmin()) && verifyGroupFieldTypes())) && verifyCreateFields(getPossibleGroupFields()); - allow update: if isSignedIn() && ((isSignedInUser() && getRequestField("role", "") == "member") || ((getGroupRole(groupId) == "owner" || isAdmin()) && verifyGroupFieldTypes())) && verifyUpdateFields(getPossibleGroupFields()); + allow create: if isSignedIn() && isSignedInUser() && (getRequestField("role", "") == "member" || (isAdmin() && verifyGroupFieldTypes())) && verifyCreateFields(getPossibleGroupFields()); + allow update: if isSignedIn() && ((getGroupRole(groupId) == "owner" || isAdmin()) && verifyGroupFieldTypes()) && verifyUpdateFields(getPossibleGroupFields()); } } @@ -91,54 +93,21 @@ service cloud.firestore { } function getPossibleFields() { - let requiredFields = ["display_name"]; - let optionalFields = []; - let allFields = requiredFields.concat(optionalFields); - return [requiredFields, allFields]; + 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 create: if isSignedIn() && getGroupRole(groupId) == "owner" && verifyCreateFields(getPossibleFields()) && verifyFieldTypes(); - allow update: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()) && verifyUpdateFields(getPossibleFields()) && verifyFieldTypes(); + allow update: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()) && verifyUpdateFields(getPossibleUpdateFields()) && verifyFieldTypes(); allow delete: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()); - - match /sets/{setId} { - function isSetVisibleToUser(setId) { - return (isPublicSet(setId) || isSetOwner(setId)); - } - - function verifySetFieldTypes() { - return getRequestField("exists", true); - } - - function getPossibleSetFields() { - let requiredFields = ["exists"]; - let optionalFields = []; - let allFields = requiredFields.concat(optionalFields); - return [requiredFields, allFields]; - } - - - allow read: if isSignedIn() && ((getGroupRole(groupId) != null) || isAdmin()); - allow create: if isSignedIn() && isSetVisibleToUser(setId) && (getGroupRole(groupId) == "contributor" || getGroupRole(groupId) == "owner" || isAdmin()) && verifyCreateFields(getPossibleSetFields()) && verifySetFieldTypes(); - allow delete: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()); - } - - match /static/data { - function verifyStaticFieldTypes() { - return verifyStringType("join_code"); - } - - function getPossibleStaticFields() { - let requiredFields = ["join_code"]; - let optionalFields = []; - let allFields = requiredFields.concat(optionalFields); - return [requiredFields, allFields]; - } - - allow read, delete: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()); - allow create: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()) && verifyCreateFields(getPossibleStaticFields()) && verifyStaticFieldTypes(); - } } @@ -146,29 +115,28 @@ service cloud.firestore { function verifyFieldTypes() { return verifyBoolType("public") && verifyStringType("title") && - verifyStringType("owner"); + verifyStringType("owner") && + verifyNullType("groups"); } function getPossibleFields() { let nonStaticFields = ["public", "title"]; - let staticFields = ["owner"]; - let requiredFields = staticFields.concat(nonStaticFields); - let optionalFields = []; - let allFields = requiredFields.concat(optionalFields); - return [requiredFields, allFields, nonStaticFields]; + let staticFields = ["owner", "groups"]; + + let allFields = staticFields.concat(nonStaticFields); + return [nonStaticFields, allFields]; } function getPossibleCreateFields() { let fields = getPossibleFields(); - return [fields[0], fields[1]]; + return [fields[1], fields[1]]; } function getPossibleUpdateFields() { let fields = getPossibleFields(); - return [[], fields[2]]; + return [[], fields[0]]; } - allow read, delete: if isSignedIn() && request.auth.uid == resource.data.owner; allow read: if isSignedIn() && resource.data.public; allow create: if isSignedIn() && request.auth.uid == request.resource.data.owner && verifyCreateFields(getPossibleCreateFields()) && verifyFieldTypes(); @@ -210,19 +178,17 @@ service cloud.firestore { allow read: if isSignedIn() && isProgressUser(); allow delete: if isSignedIn() && isProgressUser() && isNotComplete(); - // TODO: update and create disallowed as these are handled by Cloud Functions to ensure sound file Ids aren't altered to illegally access other files - // TODO: disallow start_time update + // NOTE: update and create disallowed as these are handled by Cloud Functions to ensure sound file Ids aren't altered to illegally access other files + // NOTE: disallow start_time update match /terms/{vocabId} { allow read: if isSignedIn() && isProgressUser() && !(isLanguageSwitched()); - // TODO: create handled by Cloud Functions - allow delete: if isSignedIn() && isProgressUser() && isNotComplete(); + // NOTE: create handled by Cloud Functions } match /definitions/{vocabId} { allow read: if isSignedIn() && isProgressUser() && isLanguageSwitched(); - // TODO: create handled by Cloud Functions - allow delete: if isSignedIn() && isProgressUser() && isNotComplete(); + // NOTE: create handled by Cloud Functions } } }