【TeamRole enum】Firestoreのrole文字列をDartの権限として扱う
このページでやること
このページでは、Firestoreに保存している role の文字列を、Dart側で安全に扱えるようにします。
Firestoreには、次のように保存しています。
role: owner
これを、Dartでは TeamRole という形で扱います。
TeamRole.owner
こうすることで、権限チェックのコードが分かりやすくなります。
今日のゴール
今まで文字列で見ていた role を、Dartの enum に変換します。
Firestore
↓
role: owner
Dart
↓
TeamRole.owner
最終的に、次のように書けるようにします。
if (role.canDeleteTask) {
// タスク削除OK
}
このページで出てくる単語
| 単語 | 一言説明 |
|---|---|
enum | 決まった種類だけを扱うための型 |
TeamRole | チーム内の権限を表すDartの型 |
owner | オーナー |
admin | 管理者 |
member | メンバー |
viewer | 閲覧者 |
extension | enumに便利機能を追加する書き方 |
fromString | 文字列からenumに変換する関数 |
npmや環境変数は必要?
このページでは、npmは使いません。
環境変数も設定しません。
このページでやることは、Dartコードを追加するだけです。
main.dartを開く
↓
TeamRole enumを作る
↓
Firestoreのrole文字列をTeamRoleに変換する
↓
権限チェックを読みやすくする
Step 1:main.dartを開く
次のファイルを開きます。
lib/main.dart
ターミナルから開く場合はこちらです。
code lib/main.dart
Step 2:enumとは何かを理解する
enum は、使える値を決めておくための書き方です。
今回は、権限はこの4つだけです。
owner
admin
member
viewer
そのため、Dartでは次のように書きます。
enum TeamRole {
owner,
admin,
member,
viewer,
}
これで、TeamRole には4つの値しか入らなくなります。
Step 3:TeamRoleを追加する
AppColors や TeamCard より上のあたりに、次のコードを追加します。
enum TeamRole {
owner,
admin,
member,
viewer,
}
これだけで、Dart側で権限を型として扱えるようになります。
TeamRole.owner
TeamRole.admin
TeamRole.member
TeamRole.viewer
Step 4:Firestoreの文字列をTeamRoleに変換する
Firestoreから取れる role は文字列です。
owner
admin
member
viewer
これを TeamRole に変える関数を作ります。
TeamRole の下に、次のコードを追加します。
TeamRole teamRoleFromString(String? value) {
switch (value) {
case 'owner':
return TeamRole.owner;
case 'admin':
return TeamRole.admin;
case 'viewer':
return TeamRole.viewer;
case 'member':
default:
return TeamRole.member;
}
}
これで、文字列をDartの権限に変換できます。
'owner' → TeamRole.owner
'admin' → TeamRole.admin
'member' → TeamRole.member
'viewer' → TeamRole.viewer
知らない値が来た場合は、安全のため member にします。
Step 5:TeamRoleを日本語表示に変える
画面では、日本語で表示した方が分かりやすいです。
次のコードを追加します。
String teamRoleLabel(TeamRole role) {
switch (role) {
case TeamRole.owner:
return 'オーナー';
case TeamRole.admin:
return '管理者';
case TeamRole.member:
return 'メンバー';
case TeamRole.viewer:
return '閲覧者';
}
}
これで、画面表示を変換できます。
TeamRole.owner → オーナー
TeamRole.admin → 管理者
TeamRole.member → メンバー
TeamRole.viewer → 閲覧者
Step 6:権限チェック用の関数を作る
次に、できる操作を判定する関数を作ります。
まずはタスク削除です。
bool canDeleteTaskByRole(TeamRole role) {
return role == TeamRole.owner || role == TeamRole.admin;
}
意味はこうです。
owner → タスク削除OK
admin → タスク削除OK
member → タスク削除NG
viewer → タスク削除NG
Step 7:タスク作成できるか判定する
タスク作成は、owner、admin、member ができます。
bool canCreateTaskByRole(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
viewer は作成できません。
Step 8:タスク編集できるか判定する
タスク編集も、最初は owner、admin、member ができる設計にします。
bool canEditTaskByRole(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
Step 9:メンバー管理できるか判定する
メンバー管理は、owner と admin だけにします。
bool canManageMembersByRole(TeamRole role) {
return role == TeamRole.owner || role == TeamRole.admin;
}
Step 10:チーム削除できるか判定する
チーム削除は強い操作なので、owner だけにします。
bool canDeleteTeamByRole(TeamRole role) {
return role == TeamRole.owner;
}
Step 11:ここまでの完成コード
TeamRole 周りのコードは、次の形になります。
enum TeamRole {
owner,
admin,
member,
viewer,
}
TeamRole teamRoleFromString(String? value) {
switch (value) {
case 'owner':
return TeamRole.owner;
case 'admin':
return TeamRole.admin;
case 'viewer':
return TeamRole.viewer;
case 'member':
default:
return TeamRole.member;
}
}
String teamRoleLabel(TeamRole role) {
switch (role) {
case TeamRole.owner:
return 'オーナー';
case TeamRole.admin:
return '管理者';
case TeamRole.member:
return 'メンバー';
case TeamRole.viewer:
return '閲覧者';
}
}
bool canCreateTaskByRole(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
bool canEditTaskByRole(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
bool canDeleteTaskByRole(TeamRole role) {
return role == TeamRole.owner || role == TeamRole.admin;
}
bool canManageMembersByRole(TeamRole role) {
return role == TeamRole.owner || role == TeamRole.admin;
}
bool canDeleteTeamByRole(TeamRole role) {
return role == TeamRole.owner;
}
まずは、この形でOKです。
Step 12:getMyRoleをTeamRole対応にする
前のページでは、自分のroleを文字列で取得していました。
Future<String?> getMyRole() async {
...
}
これを、TeamRole を返す形に変えます。
Future<TeamRole> getMyRole() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) {
return TeamRole.viewer;
}
final memberDoc = await FirebaseFirestore.instance
.collection('teams')
.doc(widget.teamId)
.collection('members')
.doc(user.uid)
.get();
final data = memberDoc.data();
final roleText = data?['role']?.toString();
return teamRoleFromString(roleText);
}
ログイン状態が分からない場合は、安全のため viewer にします。
不明な人
↓
見るだけ扱い
Step 13:タスク削除チェックを変更する
前のコードでは、このように書いていました。
final role = await getMyRole();
if (!canDeleteTask(role)) {
showNoPermissionMessage();
return false;
}
これを、次のように変更します。
final role = await getMyRole();
if (!canDeleteTaskByRole(role)) {
showNoPermissionMessage();
return false;
}
role が TeamRole になったので、文字列ではなくDartの権限として判定できます。
Step 14:SnackBarの表示を少し分かりやすくする
権限がないときのメッセージは、次のままでOKです。
void showNoPermissionMessage() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('この操作を行う権限がありません。'),
),
);
}
タスク削除専用にするなら、次でもOKです。
void showNoPermissionMessage() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('タスクを削除できるのはオーナーまたは管理者のみです。'),
),
);
}
Step 15:保存する
main.dart を保存します。
Macの場合:
command + S
Windowsの場合:
Ctrl + S
Step 16:実行する
ターミナルで実行します。
flutter run
すでに起動している場合は、ターミナルで r を押します。
r
うまく反映されない場合は、R を押します。
R
Step 17:動作確認する
アプリを開きます。
ログイン
↓
トーク一覧
↓
チームをタップ
↓
タスク一覧
↓
タスクを左スワイプ
自分の role が owner または admin の場合、削除確認が出ればOKです。
タスクを削除しますか?
member または viewer の場合、削除できなければOKです。
この操作を行う権限がありません。
Step 18:Firestoreでroleを変えて確認する
Firebase Consoleを開きます。
https://console.firebase.google.com/
次の場所を開きます。
Firestore Database
↓
teams
↓
作成したteamId
↓
members
↓
自分のuid
role を変更して確認します。
owner → 削除できる
admin → 削除できる
member → 削除できない
viewer → 削除できない
よくあるエラーと直し方
| エラー | 原因 | 直し方 |
|---|---|---|
TeamRole isn't defined | enumを書いていない | enum TeamRole を追加 |
teamRoleFromString isn't defined | 変換関数がない | teamRoleFromString() を追加 |
canDeleteTaskByRole isn't defined | 判定関数がない | canDeleteTaskByRole() を追加 |
Future<TeamRole>でエラー | 戻り値がStringのまま | return teamRoleFromString(roleText); にする |
| ずっとmember扱いになる | Firestoreのrole文字列が違う | owner/admin/member/viewer にする |
| 削除できない | roleがmember/viewer | Firestoreのroleを確認 |
permission-denied | Firestore Rulesで拒否 | 開発用Rulesを確認 |
role文字列が間違っているとき
Firestoreの role が次のように間違っているとします。
role: administrator
この場合、teamRoleFromString() では member 扱いになります。
case 'member':
default:
return TeamRole.member;
最初は、Firestoreの値を必ずこの4つにそろえてください。
owner
admin
member
viewer
viewerを安全な初期値にする考え方
ログインしていない場合や、メンバー情報がない場合は、強い権限を与えない方が安全です。
そのため、次のように viewer にする考え方もあります。
if (user == null) {
return TeamRole.viewer;
}
ただし、知らない文字列が来た場合は、今回は member にしています。
より安全にしたい場合は、ここも viewer にしてOKです。
case 'viewer':
default:
return TeamRole.viewer;
安全重視なら、default は viewer がおすすめです。
最短作業まとめ
読むのが大変な人は、ここだけ見てください。
1. TeamRoleを作る
enum TeamRole {
owner,
admin,
member,
viewer,
}
2. 文字列からTeamRoleに変える
TeamRole teamRoleFromString(String? value) {
switch (value) {
case 'owner':
return TeamRole.owner;
case 'admin':
return TeamRole.admin;
case 'viewer':
return TeamRole.viewer;
case 'member':
default:
return TeamRole.member;
}
}
3. タスク削除できるか判定する
bool canDeleteTaskByRole(TeamRole role) {
return role == TeamRole.owner || role == TeamRole.admin;
}
4. getMyRoleを変更する
Future<TeamRole> getMyRole() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) {
return TeamRole.viewer;
}
final memberDoc = await FirebaseFirestore.instance
.collection('teams')
.doc(widget.teamId)
.collection('members')
.doc(user.uid)
.get();
final data = memberDoc.data();
final roleText = data?['role']?.toString();
return teamRoleFromString(roleText);
}
5. 保存して実行
flutter run
チェックリスト
□ main.dartを開いた
□ TeamRole enumを作った
□ ownerを追加した
□ adminを追加した
□ memberを追加した
□ viewerを追加した
□ teamRoleFromString()を作った
□ teamRoleLabel()を作った
□ canCreateTaskByRole()を作った
□ canEditTaskByRole()を作った
□ canDeleteTaskByRole()を作った
□ canManageMembersByRole()を作った
□ canDeleteTeamByRole()を作った
□ getMyRole()をFuture<TeamRole>にした
□ タスク削除の判定をTeamRoleで行った
□ 保存した
□ flutter runで確認した
ミニ確認問題
Q1. enum は何のために使いますか?
回答
決まった種類だけを安全に扱うために使います。
今回なら、owner、admin、member、viewer だけを扱います。
Q2. Firestoreの role: owner はDartでは何に変換しますか?
回答
TeamRole.owner に変換します。
Q3. タスク削除できるTeamRoleはどれですか?
回答
TeamRole.owner と TeamRole.admin です。
Q4. teamRoleFromString() は何をする関数ですか?
回答
Firestoreの文字列を、Dartの TeamRole に変換する関数です。
Q5. このページでnpmや環境変数は必要ですか?
回答
必要ありません。
このページでは、Dartの enum と変換関数を追加するだけです。
このページのまとめ
- Firestoreには
roleを文字列で保存する。 - Dart側では
TeamRoleenumとして扱う。 teamRoleFromString()で文字列からenumに変換する。teamRoleLabel()で日本語表示に変換する。canDeleteTaskByRole()のように権限判定を関数化する。- 文字列のまま判定するより、enumにした方が読みやすくなる。
- 本番ではFlutter側だけでなく、Firestore Security Rules側にも権限制御を書く。
- このページではnpmや環境変数は不要。
次のページでやること
次のページでは、メンバー一覧画面を作ります。
teams/{teamId}/members を取得して、チームに参加しているユーザーを表示します。
