【権限判定】canCreateTask・canEditTask・canDeleteTaskを作る
このページでやること
このページでは、ログイン中の人が「タスクを作れるか・編集できるか・削除できるか」を判定する関数を作ります。
使う関数は、この3つです。
canCreateTask()
canEditTask()
canDeleteTask()
権限は、前のページで作った TeamRole を使います。
owner
admin
member
viewer
今日のゴール
次のように、権限ごとにできる操作を分けます。
| role | タスク作成 | タスク編集 | タスク削除 |
|---|---|---|---|
owner | ○ | ○ | ○ |
admin | ○ | ○ | ○ |
member | ○ | ○ | × |
viewer | × | × | × |
つまり、ルールはこうです。
owner → 作成・編集・削除できる
admin → 作成・編集・削除できる
member → 作成・編集できる。削除はできない
viewer → 見るだけ
npmや環境変数は必要?
このページでは、npmは使いません。
環境変数も設定しません。
main.dart にDartの関数を追加するだけです。
main.dartを開く
↓
TeamRoleを確認する
↓
canCreateTaskを作る
↓
canEditTaskを作る
↓
canDeleteTaskを作る
↓
画面の操作に使う
Step 1:main.dartを開く
次のファイルを開きます。
lib/main.dart
ターミナルから開く場合はこちらです。
code lib/main.dart
Step 2:TeamRole enumを確認する
前のページで作った TeamRole があるか確認します。
enum TeamRole {
owner,
admin,
member,
viewer,
}
これがない場合は、先に追加します。
TeamRole は、チーム内の権限を表す型です。
Step 3:canCreateTaskを作る
まず、タスクを作成できるかを判定します。
TeamRole の下あたりに、次の関数を追加します。
bool canCreateTask(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
意味はこうです。
owner → 作成できる
admin → 作成できる
member → 作成できる
viewer → 作成できない
viewer は見るだけなので、タスク作成はできません。
Step 4:canEditTaskを作る
次に、タスクを編集できるかを判定します。
bool canEditTask(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
意味はこうです。
owner → 編集できる
admin → 編集できる
member → 編集できる
viewer → 編集できない
最初の設計では、member もタスク編集できるようにします。
Step 5:canDeleteTaskを作る
次に、タスクを削除できるかを判定します。
bool canDeleteTask(TeamRole role) {
return role == TeamRole.owner || role == TeamRole.admin;
}
意味はこうです。
owner → 削除できる
admin → 削除できる
member → 削除できない
viewer → 削除できない
削除は強い操作なので、owner と admin だけにします。
Step 6:3つの関数の完成形
ここまでの完成形です。
bool canCreateTask(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
bool canEditTask(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
bool canDeleteTask(TeamRole role) {
return role == TeamRole.owner || role == TeamRole.admin;
}
この3つを作っておくと、画面側のコードが読みやすくなります。
Step 7:なぜ関数にするのか
毎回このように書くと、コードが長くなります。
if (role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member) {
// タスク作成OK
}
関数にすると、短く書けます。
if (canCreateTask(role)) {
// タスク作成OK
}
読みやすくなります。
canCreateTask(role)
↓
この人はタスクを作れる?
Step 8: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);
}
この関数で、Firestoreの role を取得します。
見る場所はこちらです。
teams/{teamId}/members/{uid}
Step 9:タスク作成ボタンで使う
タスク作成は、右下の + ボタンで行います。
今はこのようになっているはずです。
floatingActionButton: FloatingActionButton(
onPressed: showCreateTaskSheet,
child: const Icon(Icons.add),
),
これを、権限チェック付きに変更します。
floatingActionButton: FloatingActionButton(
onPressed: () async {
final role = await getMyRole();
if (!canCreateTask(role)) {
showNoPermissionMessage();
return;
}
showCreateTaskSheet();
},
child: const Icon(Icons.add),
),
これで、viewer はタスク作成できなくなります。
Step 10:タスク編集で使う
タスクをタップしたときの編集処理にも、権限チェックを入れます。
今はこのようになっているはずです。
onTap: () {
showEditTaskSheet(
taskId: doc.id,
currentTitle: title,
currentDescription: description,
currentAssigneeEmail: assigneeEmail,
currentPriority: priority,
currentStatus: status,
);
},
これを次のように変更します。
onTap: () async {
final role = await getMyRole();
if (!canEditTask(role)) {
showNoPermissionMessage();
return;
}
showEditTaskSheet(
taskId: doc.id,
currentTitle: title,
currentDescription: description,
currentAssigneeEmail: assigneeEmail,
currentPriority: priority,
currentStatus: status,
);
},
これで、viewer は編集できなくなります。
Step 11:タスク削除で使う
タスク削除は、左スワイプの confirmDismiss で判定します。
今はこのようになっているはずです。
confirmDismiss: (_) async {
final role = await getMyRole();
if (!canDeleteTask(role)) {
showNoPermissionMessage();
return false;
}
return confirmDeleteTask(title);
},
この形になっていればOKです。
流れはこうです。
左スワイプ
↓
自分のroleを確認
↓
削除できる権限か確認
↓
OKなら削除確認
↓
NGなら削除しない
Step 12:権限なしメッセージを用意する
権限がないときに出すメッセージも確認します。
void showNoPermissionMessage() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('この操作を行う権限がありません。'),
),
);
}
この関数は、_TaskListPageState の中に置きます。
Step 13:保存する
main.dart を保存します。
Macの場合:
command + S
Windowsの場合:
Ctrl + S
Step 14:実行する
ターミナルで実行します。
flutter run
すでに起動している場合は、ターミナルで r を押します。
r
うまく反映されない場合は、R を押します。
R
Step 15:ownerで確認する
Firebase Consoleで、自分の role を確認します。
Firestore Database
↓
teams
↓
作成したteamId
↓
members
↓
自分のuid
role が次ならOKです。
role: owner
アプリで確認します。
タスク作成 → できる
タスク編集 → できる
タスク削除 → できる
Step 16:adminで確認する
Firestoreで role を次に変更します。
role: admin
アプリで確認します。
タスク作成 → できる
タスク編集 → できる
タスク削除 → できる
Step 17:memberで確認する
Firestoreで role を次に変更します。
role: member
アプリで確認します。
タスク作成 → できる
タスク編集 → できる
タスク削除 → できない
左スワイプ削除をしようとして、権限なしメッセージが出ればOKです。
Step 18:viewerで確認する
Firestoreで role を次に変更します。
role: viewer
アプリで確認します。
タスク作成 → できない
タスク編集 → できない
タスク削除 → できない
右下の + を押したり、タスクをタップしたりして、権限なしメッセージが出ればOKです。
よくあるエラーと直し方
| エラー | 原因 | 直し方 |
|---|---|---|
canCreateTask isn't defined | 関数がない | canCreateTask() を追加 |
canEditTask isn't defined | 関数がない | canEditTask() を追加 |
canDeleteTask isn't defined | 関数がない | canDeleteTask() を追加 |
getMyRole isn't defined | 自分の権限取得関数がない | getMyRole() を追加 |
TeamRole isn't defined | enumがない | enum TeamRole を追加 |
| viewerでも作成できる | FABに権限チェックがない | canCreateTask(role) を入れる |
| viewerでも編集できる | onTapに権限チェックがない | canEditTask(role) を入れる |
| memberでも削除できる | confirmDismissに権限チェックがない | canDeleteTask(role) を入れる |
注意:Flutter側だけでは本番の安全対策にならない
このページでは、Flutter側で操作を止めています。
これは、画面上の使いやすさには必要です。
ただし、本番ではFirestore Security Rulesにも必ず書きます。
Flutter側
↓
ボタンを押せないようにする
操作時にメッセージを出す
Firestore Rules側
↓
本当にデータを守る
本番では、Rulesが最終防衛ラインです。
最短作業まとめ
読むのが大変な人は、ここだけ見てください。
1. 権限判定を作る
bool canCreateTask(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
bool canEditTask(TeamRole role) {
return role == TeamRole.owner ||
role == TeamRole.admin ||
role == TeamRole.member;
}
bool canDeleteTask(TeamRole role) {
return role == TeamRole.owner || role == TeamRole.admin;
}
2. 作成ボタンで使う
onPressed: () async {
final role = await getMyRole();
if (!canCreateTask(role)) {
showNoPermissionMessage();
return;
}
showCreateTaskSheet();
},
3. 編集タップで使う
onTap: () async {
final role = await getMyRole();
if (!canEditTask(role)) {
showNoPermissionMessage();
return;
}
showEditTaskSheet(
taskId: doc.id,
currentTitle: title,
currentDescription: description,
currentAssigneeEmail: assigneeEmail,
currentPriority: priority,
currentStatus: status,
);
},
4. 削除で使う
confirmDismiss: (_) async {
final role = await getMyRole();
if (!canDeleteTask(role)) {
showNoPermissionMessage();
return false;
}
return confirmDeleteTask(title);
},
チェックリスト
□ main.dartを開いた
□ TeamRole enumを確認した
□ canCreateTask()を作った
□ canEditTask()を作った
□ canDeleteTask()を作った
□ getMyRole()を確認した
□ showNoPermissionMessage()を用意した
□ FloatingActionButtonにcanCreateTaskを入れた
□ TaskCardのonTapにcanEditTaskを入れた
□ DismissibleのconfirmDismissにcanDeleteTaskを入れた
□ ownerで作成・編集・削除できることを確認した
□ adminで作成・編集・削除できることを確認した
□ memberで削除できないことを確認した
□ viewerで作成・編集・削除できないことを確認した
ミニ確認問題
Q1. タスク作成できるroleはどれですか?
回答
owner、admin、member です。
Q2. タスク編集できるroleはどれですか?
回答
owner、admin、member です。
Q3. タスク削除できるroleはどれですか?
回答
owner、admin です。
Q4. viewerは何ができますか?
回答
タスクを見るだけです。
作成・編集・削除はできません。
Q5. このページでnpmや環境変数は必要ですか?
回答
必要ありません。
Dartの関数を追加するだけです。
このページのまとめ
canCreateTask()でタスク作成できるか判定する。canEditTask()でタスク編集できるか判定する。canDeleteTask()でタスク削除できるか判定する。ownerとadminは作成・編集・削除できる。memberは作成・編集できるが、削除できない。viewerは見るだけ。- Flutter側の判定は使いやすさのために必要。
- 本番ではFirestore Security Rulesにも必ず権限制御を書く。
- このページではnpmや環境変数は不要。
次のページでやること
次のページでは、メンバー一覧画面を作ります。
teams/{teamId}/members を取得して、チームに参加しているユーザーを表示します。
