テスト駆動で学ぶ Firestore セキュリティルール 【カスタム関数編:第3回】
IT技術
【第3回】テスト駆動で成績データコレクションのルールを実装する
前回の記事では、成績コレクション records のデータ追加ルールを実装すると共に、カスタム関数を利用したルールの整理について解説してみました。
今回は、成績コレクション records のデータ更新ルールの実装を進めると共に、引き続き、カスタム関数を利用したルールの整理について解説していきます。
前回の記事はこちら
前回の記事は、こちらをご参照ください。
Firestore 上のデータ更新ルールの実装について
テストの作成前に、まずは、データ更新の要件を再確認します。
admin ユーザの更新要件
- id の更新不可
- studentId の更新不可
- season の更新不可
- record の更新不可
teacher ユーザの更新要件
- 担任でないデータは更新不可
- 成績以外のフィールドは更新不可
- 対象シーズンが成績評価期間外の場合は更新不可
その他の更新要件
- student ユーザは更新不可
- 所定のフォーマットでないデータは更新不可
上記の要件に沿って、テストを作成していきます。
tests/data.update.test.ts ファイルを追加して、以下のようにコードを記述してください。
テストのコード
1process.env.FIRESTORE_EMULATOR_HOST = "localhost:58080";
2
3import { FirestoreTestSupporter } from "firestore-test-supporter";
4import * as path from "path";
5import * as firebase from "@firebase/testing";
6import { users } from "./data/collections/users";
7import { records } from "./data/collections/records";
8import { seasons } from "./data/collections/seasons";
9import InitialData from "./data/InitialData";
10
11describe("成績データの更新テスト", () => {
12 const rulesFilePath = path.join(__dirname, "firestore.rules");
13 const supporter = new FirestoreTestSupporter("my-test-project", rulesFilePath);
14
15 beforeEach(async () => {
16 await supporter.loadRules();
17
18 // 各コレクションに初期データを追加
19 const initialData = new InitialData(rulesFilePath);
20 await initialData.setupUsers();
21 await initialData.setupRecords();
22 await initialData.setupSeasons();
23 });
24
25 afterEach(async () => {
26 await supporter.cleanup()
27 });
28
29 test('ログインしていないユーザは更新不可', async () => {
30 // 認証されていないクライアントを取得
31 const db = supporter.getFirestore();
32
33 const doc = db.collection(records.collectionPath).doc(records.initialData.id);
34 await firebase.assertFails(doc.update(records.initialData))
35 });
36
37 describe("adminユーザの更新要件", () => {
38 let doc: firebase.firestore.DocumentReference;
39 beforeEach(() => {
40 // adminユーザで認証されたクライアントを取得
41 const db = supporter.getFirestoreWithAuth(users.sample.admin);
42
43 doc = db.collection(records.collectionPath).doc(records.initialData.id);
44 });
45
46 test('要件にあったデータの更新に成功', async () => {
47 await firebase.assertSucceeds(doc.update(records.validUpdateDataForAdmin));
48 });
49
50 test('idの更新不可', async () => {
51 // リクエストデータのidを変更
52 const badData = { ...records.validUpdateDataForAdmin, id: "other_id" };
53
54 await firebase.assertFails(doc.update(badData));
55 });
56
57 test('studentIdの更新不可', async () => {
58 // リクエストデータのstudentIdを変更
59 const badData = { ...records.validUpdateDataForAdmin, studentId: "other0000" };
60
61 await firebase.assertFails(doc.update(badData));
62 });
63
64 test('seasonの更新不可', async () => {
65 // リクエストデータのseasonを変更
66 const badData = { ...records.validUpdateDataForAdmin, season: { year: 2011, semester: 3 } };
67
68 await firebase.assertFails(doc.update(badData));
69 });
70
71 test('recordの更新不可', async () => {
72 // リクエストデータのrecordを変更
73 const badData = { ...records.validUpdateDataForAdmin, record: { Math: 3, NationalLanguage: 4 } };
74
75 await firebase.assertFails(doc.update(badData));
76 });
77 });
78
79 describe("teacherユーザの更新要件", () => {
80 let doc: firebase.firestore.DocumentReference;
81 beforeEach(() => {
82 // teacherユーザで認証されたクライアントを取得
83 const db = supporter.getFirestoreWithAuth(users.sample.teacher);
84
85 doc = db.collection(records.collectionPath).doc(records.initialData.id);
86 });
87
88 test('要件にあったデータの更新に成功', async () => {
89 await firebase.assertSucceeds(doc.update(records.validUpdateDataForTeacher));
90 });
91
92 test('担任でないデータは更新不可', async () => {
93 // データベース上のデータの担任を変更
94 const dbWithAdmin = supporter.getAdminFirestore();
95 const docWithAdmin = dbWithAdmin.collection(records.collectionPath).doc(records.initialData.id);
96 const newData = { ...records.validUpdateDataForTeacher, homeroomTeacher: "other_teacher" };
97 await firebase.assertSucceeds(docWithAdmin.update(newData));
98
99 await firebase.assertFails(doc.update(records.validUpdateDataForTeacher));
100 });
101
102 describe('成績以外のフィールドは更新不可', () => {
103 test('idの更新不可', async () => {
104 // リクエストデータのidを変更
105 const badData = { ...records.validUpdateDataForTeacher, id: "other_id" };
106
107 await firebase.assertFails(doc.update(badData));
108 });
109
110 test('studentIdの更新不可', async () => {
111 // リクエストデータのstudentIdを変更
112 const badData = { ...records.validUpdateDataForTeacher, studentId: "other0000" };
113
114 await firebase.assertFails(doc.update(badData));
115 });
116
117 test('seasonの更新不可', async () => {
118 // リクエストデータのseasonを変更
119 const badData = { ...records.validUpdateDataForTeacher, season: { year: 2009, semester: 1 } };
120
121 await firebase.assertFails(doc.update(badData));
122 });
123
124 test('nameの更新不可', async () => {
125 // リクエストデータのnameを変更
126 const badData = { ...records.validUpdateDataForTeacher, name: "other_name" };
127
128 await firebase.assertFails(doc.update(badData));
129 });
130
131 test('homeroomTeacherの更新不可', async () => {
132 // リクエストデータのhomeroomTeacherを変更
133 const badData = { ...records.validUpdateDataForTeacher, homeroomTeacher: "other_id" };
134
135 await firebase.assertFails(doc.update(badData));
136 });
137 });
138
139 test('対象シーズンが成績評価期間外の場合は更新不可', async () => {
140 // データベース上のデータの成績評価期間フラグをfalseに変更
141 const dbWithAdmin = supporter.getAdminFirestore();
142 const seasonDoc = dbWithAdmin.collection(seasons.collectionPath).doc(seasons.initialData(true).id);
143 await firebase.assertSucceeds(seasonDoc.update(seasons.initialData(false)));
144
145 await firebase.assertFails(doc.update(records.validUpdateDataForTeacher));
146 });
147 });
148
149 test('studentユーザは更新不可', async () => {
150 // studentユーザで認証されたクライアントを取得
151 const db = supporter.getFirestoreWithAuth(users.sample.student);
152
153 const doc = db.collection(records.collectionPath).doc(records.initialData.id);
154 await firebase.assertFails(doc.update(records.initialData));
155 });
156});
テストの概要
「admin ユーザの更新要件」テスト
beforeEach() 関数内で、各テスト前に admin ユーザで認証されたクライアントを取得しています。
「teacher ユーザの更新要件」テスト
adminユーザの更新要件 テストと同様に、teacher ユーザで認証されたクライアントを取得しています。
担任でないデータは更新不可 となるテストでは、データ更新前に、データベース上のデータの担任を、リクエストデータの担任とは異なる担任に変更。
対象シーズンが成績評価期間外の場合は更新不可 となるテストでは、データ更新の前に、seasons コレクションの対象となる、 season ドキュメントの成績評価期間フラグを「false」に変更しています。
「所定のフォーマットでないデータは更新不可」となる要件について
データ更新については、フォーマットチェックテストが複雑になるため、解説の便宜上、テストを作成していません。
リクエストデータのフォーマットは、データ追加の場合と同じフォーマットを要件としています。
フォーマットチェックには、前回の記事で定義した、フォーマットチェック用関数 isValidFormat() を使用。
他のテストの結果に、影響がないかどうかだけを確認します。
Firestore 上のデータ更新ルールを実装してみる
それでは、作成したテストに沿って、セキュリティルールを実装していきましょう。
セキュリティルールが少し長くなってきているので、ここでは、前回の記事で実装したルールとは分けて、データ更新のルールだけを実装していきます。
そして、最後に、データ追加用のルールとデータ更新用のルールを統合したいと思います。
全ての update アクセスを許可するルールを設定
データ追加ルールの実装と同様、まずは、records コレクションの update アクセスを全て許可し、「要件にあったデータの追加に成功」するテストを通します。
その後、失敗したテストを順次、上から通していきます。
セキュリティルールの調整
以下のように、セキュリティルールを調整してください。
1rules_version = '2';
2service cloud.firestore {
3 match /databases/{database}/documents {
4 match /students/{studentId}/records/{recordId}{
5 // 全てのupdateアクセスを許可
6 allow update: if true;
7 }
8 }
9}
テストをスタート
ルールを調整したら、以下のコマンドを実行して、データ更新ルール用のテストをスタートしてください。
1npm run test-watch tests/data.update.test.ts
テスト結果
1 FAIL tests/data.update.test.ts
2 成績データの更新テスト
3 × ログインしていないユーザは更新不可 (1171ms)
4 × studentユーザは更新不可 (64ms)
5 adminユーザの更新要件
6 √ 要件にあったデータの更新に成功 (87ms)
7 × idの更新不可 (87ms)
8 × studentIdの更新不可 (68ms)
9 × seasonの更新不可 (71ms)
10 × recordの更新不可 (66ms)
11 teacherユーザの更新要件
12 √ 要件にあったデータの更新に成功 (63ms)
13 × 担任でないデータは更新不可 (1152ms)
14 × 対象シーズンが成績評価期間外の場合は更新不可 (99ms)
15 成績以外のフィールドは更新不可
16 × idの更新不可 (70ms)
17 × studentIdの更新不可 (64ms)
18 × seasonの更新不可 (65ms)
19 × nameの更新不可 (71ms)
20 × homeroomTeacherの更新不可 (67ms)
admin ユーザと teacher ユーザに対して、要件にあったデータの更新に成功 するテストが成功しました。
続いて、失敗しているテストを上から順に通していきたいと思います。
「admin, teacher ユーザ以外は更新不可」となるルールを設定
ログインしていないユーザは更新不可、studentユーザは更新不可 となるように、admin ユーザか teacher ユーザでない場合は、更新不可となるルールを追加します。
データ追加ルールの実装で定義した isAdmin() 関数と同様、新しく isTeacher() 関数を定義します。
以下のようにルールを調整してください。
1rules_version = '2';
2service cloud.firestore {
3 match /databases/{database}/documents {
4 match /students/{studentId}/records/{recordId}{
5 function isLoggedIn(){
6 return request.auth.uid != null;
7 }
8
9 function isUserRole(role){
10 return isLoggedIn()
11 && role in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
12 }
13
14 function isAdmin(){
15 return isUserRole("admin");
16 }
17
18 // teacherロールチェック
19 function isTeacher(){
20 return isUserRole("teacher");
21 }
22
23 allow update: if isAdmin();
24
25 allow update: if isTeacher();
26 }
27 }
28}
ここで、isTeacher() 関数では、データ追加ルールの実装の最後で定義したロールチェック用関数 isUserRole() を使用し、リクエストユーザが teacher ロールを持っているかどうかをチェックしています。
isAdmin() 関数と同様、isTeacher() 関数にはログインチェックも含んでいます。
また、admin ユーザと teacher ユーザで要件が異なるため、以降のルール調整に備えて、ルールを admin ユーザと teacher ユーザで分けて記述しました。
テスト結果
1 FAIL tests/data.update.test.ts
2 成績データの更新テスト
3 √ ログインしていないユーザは更新不可 (154ms)
4 √ studentユーザは更新不可 (72ms)
5 adminユーザの更新要件
6 √ 要件にあったデータの更新に成功 (109ms)
7 × idの更新不可 (121ms)
8 × studentIdの更新不可 (92ms)
9 × seasonの更新不可 (81ms)
10 × recordの更新不可 (87ms)
11 teacherユーザの更新要件
12 √ 要件にあったデータの更新に成功 (1247ms)
13 × 担任でないデータは更新不可 (130ms)
14 × 対象シーズンが成績評価期間外の場合は更新不可 (91ms)
15 成績以外のフィールドは更新不可
16 × idの更新不可 (69ms)
17 × studentIdの更新不可 (72ms)
18 × seasonの更新不可 (83ms)
19 × nameの更新不可 (77ms)
20 × homeroomTeacherの更新不可 (84ms)
ログインしていないユーザは更新不可、studentユーザは更新不可 となるテストが通りました。
「admin ユーザの更新要件」をルールに設定
続いて、adminユーザの更新要件 の更新不可テストを、まとめて通してしまいます。
以下のように、ルールを調整してください。
1rules_version = '2';
2service cloud.firestore {
3 match /databases/{database}/documents {
4 match /students/{studentId}/records/{recordId}{
5 function isLoggedIn(){
6 return request.auth.uid != null;
7 }
8
9 function isUserRole(role){
10 return isLoggedIn()
11 && role in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
12 }
13
14 function isAdmin(){
15 return isUserRole("admin");
16 }
17
18 function isTeacher(){
19 return isUserRole("teacher");
20 }
21
22 // 指定フィールドの更新なしチェック
23 function isNotUpdate(key){
24 return request.resource.data[key] == resource.data[key]
25 }
26
27 allow update: if isAdmin()
28 && isNotUpdate("id")
29 && isNotUpdate("studentId")
30 && isNotUpdate("season")
31 && isNotUpdate("record");
32
33 allow update: if isTeacher();
34 }
35 }
36}
request.resource.data.some_field == resource.data.some_field のように記述することで、対象フィールドを変更不可とすることができます。
どのフィールドでも同様の記述となるので、重複を避けるために isNotUpdate() 関数を定義しています。
テスト結果
1 FAIL tests/data.update.test.ts
2 成績データの更新テスト
3 √ ログインしていないユーザは更新不可 (1160ms)
4 √ studentユーザは更新不可 (69ms)
5 adminユーザの更新要件
6 √ 要件にあったデータの更新に成功 (82ms)
7 √ idの更新不可 (80ms)
8 √ studentIdの更新不可 (67ms)
9 √ seasonの更新不可 (73ms)
10 √ recordの更新不可 (68ms)
11 teacherユーザの更新要件
12 √ 要件にあったデータの更新に成功 (63ms)
13 × 担任でないデータは更新不可 (94ms)
14 × 対象シーズンが成績評価期間外の場合は更新不可 (96ms)
15 成績以外のフィールドは更新不可
16 × idの更新不可 (65ms)
17 × studentIdの更新不可 (68ms)
18 × seasonの更新不可 (60ms)
19 × nameの更新不可 (1122ms)
20 × homeroomTeacherの更新不可 (69ms)
adminユーザの更新要件 テストが全て通ったので、これで admin ユーザに対するルール設定は完了です。
「teacher ユーザの更新要件」をルールに設定
続いて、teacherユーザの更新要件 テストを通して行きます。
担任でないデータは更新不可、対象シーズンが成績評価期間外の場合は更新不可 となるよう、以下のようにルールを調整してください。
1rules_version = '2';
2service cloud.firestore {
3 match /databases/{database}/documents {
4 match /students/{studentId}/records/{recordId}{
5 function isLoggedIn(){
6 return request.auth.uid != null;
7 }
8
9 function isUserRole(role){
10 return isLoggedIn()
11 && role in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
12 }
13
14 function isAdmin(){
15 return isUserRole("admin");
16 }
17
18 function isTeacher(){
19 return isUserRole("teacher");
20 }
21
22 function isNotUpdate(key){
23 return request.resource.data[key] == resource.data[key]
24 }
25
26 // 担任チェック
27 function isHomeroomTeacher(){
28 return request.auth.uid == resource.data.homeroomTeacher
29 }
30
31 // 成績評価期間チェック
32 function isEvaluationPeriod(){
33 return get(/databases/$(database)/documents/seasons/$(resource.data.season)).data.evaluationPeriod
34 }
35
36 allow update: if isAdmin()
37 && isNotUpdate("id")
38 && isNotUpdate("studentId")
39 && isNotUpdate("season")
40 && isNotUpdate("record");
41
42 allow update: if isTeacher()
43 && isHomeroomTeacher()
44 && isEvaluationPeriod();
45 }
46 }
47}
ここで、isHomeroomTeacher() 関数により、リクエストユーザが対象となる成績データの担任であるかどうかを判定しています。
isEvaluationPeriod() 関数は、seasons コレクションの対象となる season ドキュメントを参照して、成績評価期間フラグをチェックしています。
テスト結果
1 FAIL tests/data.update.test.ts (5.181s)
2 成績データの更新テスト
3 √ ログインしていないユーザは更新不可 (2122ms)
4 √ studentユーザは更新不可 (64ms)
5 adminユーザの更新要件
6 √ 要件にあったデータの更新に成功 (94ms)
7 √ idの更新不可 (76ms)
8 √ studentIdの更新不可 (75ms)
9 √ seasonの更新不可 (71ms)
10 √ recordの更新不可 (62ms)
11 teacherユーザの更新要件
12 √ 要件にあったデータの更新に成功 (68ms)
13 √ 担任でないデータは更新不可 (91ms)
14 √ 対象シーズンが成績評価期間外の場合は更新不可 (86ms)
15 成績以外のフィールドは更新不可
16 × idの更新不可 (1108ms)
17 × studentIdの更新不可 (72ms)
18 × seasonの更新不可 (69ms)
19 × nameの更新不可 (63ms)
20 × homeroomTeacherの更新不可 (68ms)
担任でないデータは更新不可 となるテストと、対象シーズンが成績評価期間外の場合は更新不可 となるテストが通りました。
「成績以外のフィールドは更新不可」となるルールを設定
最後に、成績以外のフィールドは更新不可 となるテストをまとめて通してしまいます。
先ほど定義した isNotUpdate() 関数を使って、以下のようにルールを調整してください。
1rules_version = '2';
2service cloud.firestore {
3 match /databases/{database}/documents {
4 match /students/{studentId}/records/{recordId}{
5 function isLoggedIn(){
6 return request.auth.uid != null;
7 }
8
9 function isUserRole(role){
10 return isLoggedIn()
11 && role in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
12 }
13
14 function isAdmin(){
15 return isUserRole("admin");
16 }
17
18 function isTeacher(){
19 return isUserRole("teacher");
20 }
21
22 function isNotUpdate(key){
23 return request.resource.data[key] == resource.data[key]
24 }
25
26 function isHomeroomTeacher(){
27 return request.auth.uid == resource.data.homeroomTeacher
28 }
29
30 function isEvaluationPeriod(){
31 return get(/databases/$(database)/documents/seasons/$(resource.data.season)).data.evaluationPeriod
32 }
33
34 allow update: if isAdmin()
35 && isNotUpdate("id")
36 && isNotUpdate("studentId")
37 && isNotUpdate("season")
38 && isNotUpdate("record");
39
40 allow update: if isTeacher()
41 && isHomeroomTeacher()
42 && isEvaluationPeriod()
43 && isNotUpdate("id")
44 && isNotUpdate("studentId")
45 && isNotUpdate("season")
46 && isNotUpdate("name")
47 && isNotUpdate("homeroomTeacher");
48 }
49 }
50}
テスト結果
1 PASS tests/data.update.test.ts
2 成績データの更新テスト
3 √ ログインしていないユーザは更新不可 (100ms)
4 √ studentユーザは更新不可 (91ms)
5 adminユーザの更新要件
6 √ 要件にあったデータの更新に成功 (89ms)
7 √ idの更新不可 (81ms)
8 √ studentIdの更新不可 (84ms)
9 √ seasonの更新不可 (70ms)
10 √ recordの更新不可 (72ms)
11 teacherユーザの更新要件
12 √ 要件にあったデータの更新に成功 (70ms)
13 √ 担任でないデータは更新不可 (88ms)
14 √ 対象シーズンが成績評価期間外の場合は更新不可 (90ms)
15 成績以外のフィールドは更新不可
16 √ idの更新不可 (62ms)
17 √ studentIdの更新不可 (63ms)
18 √ seasonの更新不可 (63ms)
19 √ nameの更新不可 (75ms)
20 √ homeroomTeacherの更新不可 (70ms)
全てのテストが通ったので、これで要件に沿ったデータ更新用のルールを実装することができました。
カスタム関数でルールを整理
データ追加ルールの実装のときと同じように、今回もテスト環境を活かしてルールをもう少しだけ調整・整理したいと思います。
isNotUpdateImmutables() 関数を定義
record ドキュメントのフォーマット定義で変更不可としたフィールドは、当然ながら、admin ユーザと teacher ユーザの両方が更新不可となります。
このため、「allow式」中で、変更不可チェック用に追加した条件が重複しています。
これらのフィールドの不変チェックをひとまとめにして、isNotUpdateImmutables() 関数を定義します。
リクエストデータのフォーマットチェック
また、リクエストデータのフォーマットは、データ追加の場合と同じフォーマットです。
そのため、データ追加ルールの実装時に定義した isValidFormat() 関数を使い、リクエストデータのフォーマットチェックを行います。
テストが複雑になるため、更新ルール用のテストでは、フォーマットチェックテストは行っていません。
ルールにフォーマットチェック項目を追加した後、テストの結果に影響がないかだけ確認したいと思います。
以下のようにルールを調整してください。
1rules_version = '2';
2service cloud.firestore {
3 match /databases/{database}/documents {
4 match /students/{studentId}/records/{recordId}{
5 function isLoggedIn(){
6 return request.auth.uid != null;
7 }
8
9 function isUserRole(role){
10 return isLoggedIn()
11 && role in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
12 }
13
14 function isAdmin(){
15 return isUserRole("admin");
16 }
17
18 function isTeacher(){
19 return isUserRole("teacher");
20 }
21
22 function isNotUpdate(key){
23 return request.resource.data[key] == resource.data[key]
24 }
25
26 function isHomeroomTeacher(){
27 return request.auth.uid == resource.data.homeroomTeacher
28 }
29
30 function isEvaluationPeriod(){
31 return get(/databases/$(database)/documents/seasons/$(resource.data.season)).data.evaluationPeriod
32 }
33
34 // 変更不可フィールドの不変チェック
35 function isNotUpdateImmutables(){
36 return isNotUpdate("id")
37 && isNotUpdate("studentId")
38 && isNotUpdate("season")
39 }
40
41 function isValidFormat(){
42 return request.resource.data.id is string
43 && request.resource.data.studentId is string
44 && request.resource.data.name is string
45 && request.resource.data.season is string
46 && request.resource.data.homeroomTeacher is string
47 && request.resource.data.record is map
48 }
49
50 allow update: if isAdmin()
51 && isNotUpdateImmutables()
52 && isNotUpdate("record")
53 && isValidFormat();
54
55 allow update: if isTeacher()
56 && isHomeroomTeacher()
57 && isNotUpdateImmutables()
58 && isNotUpdate("name")
59 && isNotUpdate("homeroomTeacher")
60 && isEvaluationPeriod()
61 && isValidFormat();
62 }
63 }
64}
create アクセス、update アクセスで共通して変更不可である id、studentId、season フィールドの更新不可チェックを isNotUpdateImmutables() 関数にまとめました。
また、admin ユーザと teacher ユーザのルールに、リクエストデータのフォーマットチェック関数 isValidFormat() を追加しました。
テスト結果
1 PASS tests/data.update.test.ts
2 成績データの更新テスト
3 √ ログインしていないユーザは更新不可 (95ms)
4 √ studentユーザは更新不可 (67ms)
5 adminユーザの更新要件
6 √ 要件にあったデータの更新に成功 (85ms)
7 √ idの更新不可 (75ms)
8 √ studentIdの更新不可 (70ms)
9 √ seasonの更新不可 (72ms)
10 √ recordの更新不可 (69ms)
11 teacherユーザの更新要件
12 √ 要件にあったデータの更新に成功 (65ms)
13 √ 担任でないデータは更新不可 (98ms)
14 √ 対象シーズンが成績評価期間外の場合は更新不可 (91ms)
15 成績以外のフィールドは更新不可
16 √ idの更新不可 (63ms)
17 √ studentIdの更新不可 (62ms)
18 √ seasonの更新不可 (64ms)
19 √ nameの更新不可 (63ms)
20 √ homeroomTeacherの更新不可 (69ms)
テスト結果に変化はありません。問題なく、ルールを調整・整理することができました。
以上でデータ更新用ルールの実装は完了です。
次のテストとの競合を避けるため、テストを終了してください。
ここまでのルールの統合とリグレッションテスト
最後に、今回実装したデータ更新用のルールと前回のデータ追加用のルールを統合します。
ここまでのセキュリティルールを統合すると、以下のようになります。
統合したセキュリティルール
1rules_version = '2';
2service cloud.firestore {
3 match /databases/{database}/documents {
4 match /students/{studentId}/records/{recordId}{
5 function isLoggedIn(){
6 return request.auth.uid != null;
7 }
8
9 function isUserRole(role){
10 return isLoggedIn()
11 && role in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
12 }
13
14 function isAdmin(){
15 return isUserRole("admin");
16 }
17
18 function isTeacher(){
19 return isUserRole("teacher");
20 }
21
22 function isNotUpdate(key){
23 return request.resource.data[key] == resource.data[key]
24 }
25
26 function isNotUpdateImmutables(){
27 return isNotUpdate("id")
28 && isNotUpdate("studentId")
29 && isNotUpdate("season")
30 }
31
32 function isHomeroomTeacher(){
33 return request.auth.uid == resource.data.homeroomTeacher
34 }
35
36 function isEvaluationPeriod(){
37 return get(/databases/$(database)/documents/seasons/$(resource.data.season)).data.evaluationPeriod
38 }
39
40 function isRecordEmpty(_resource){
41 return _resource.data.record == {}
42 }
43
44 function isValidFormat(){
45 return request.resource.data.id is string
46 && request.resource.data.studentId is string
47 && request.resource.data.name is string
48 && request.resource.data.season is string
49 && request.resource.data.homeroomTeacher is string
50 && request.resource.data.record is map
51 }
52
53 allow create: if isAdmin()
54 && isRecordEmpty(request.resource)
55 && isValidFormat();
56
57 allow update: if isAdmin()
58 && isNotUpdateImmutables()
59 && isNotUpdate("record")
60 && isValidFormat();
61
62 allow update: if isTeacher()
63 && isHomeroomTeacher()
64 && isNotUpdateImmutables()
65 && isNotUpdate("name")
66 && isNotUpdate("homeroomTeacher")
67 && isEvaluationPeriod()
68 && isValidFormat();
69 }
70 }
71}
※ まとまりを考慮して、カスタム関数の順序を一部調整しています。
全てのテストを実行
以下のコマンドを実行し、ここまでの全てのテストを実行してください。
1npm run test
テスト結果
1 PASS tests/data.update.test.ts (6.081s)
2 PASS tests/data.create.test.ts
3
4Test Suites: 2 passed, 2 total
5Tests: 21 passed, 21 total
6Snapshots: 0 total
7Time: 6.974s, estimated 8s
8Ran all test suites.
全てのテストに成功していますので、ここまでのルールが問題なく実装できていることが確認できました!
第4回へつづく!
「カスタム関数編」第3回となるこの記事では、成績コレクション records のデータ更新用セキュリティルールを実装しつつ、カスタム関数を使ってルールを整理してみました!
次回は、成績コレクション records のデータ取得・削除用ルールの実装を進めます。
データ取得・削除用ルールの実装した後、作成した全てのテストを実行し、作成したセキュリティルールが「カスタム関数編」第1回に設定した要件に沿ったルールとなっているかを確認してみます。
次回もお楽しみに!
第4回の記事はこちら
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.08.04エンジニアの働き方 特集社員としての働き方社員としてのエンジニアの働き方とは?ライトコードのエンジニアはどんな働き方をしてるのか、まとめたいと...
2020.07.27IT・コンピューターの歴史特集IT・コンピューターの歴史をまとめていきたいと思います!弊社ブログにある記事のみで構成しているため、まだ「未完成状態」...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit