Repository navigation
fix(classroom): 連携した先生以外の Google Classroom 共有を実態で判定する (#1184) - #1185
smalruby3-editor-bot[bot] wants to merge 3 commits into
Conversation
… course
Sharing posts course work with the pressing teacher's own Google token, so Google
requires them to be a teacher of the linked course. Smalruby let any class
co-teacher press the button, so one who is not in the course hit an opaque
`google ClassroomAPI error`.
Judge by the fact, not by the Smalruby role: a new
GET /classroom-groups/{groupId}/google-course-access probes courses/{id}/teachers/me
with the caller's token (200=teacher, 404=notTeacher, anything else=unknown). A GC
co-teacher of the same course still passes. The UI disables the share button only on
a proven notTeacher and says who to ask; the probe uses an already-granted token only,
so it never opens a consent popup.
Groups now record googleClassroomLinkedBy
{sub,email} on link/re-link (falling back to the class owner for old rows) so the
message can name them, and a 404 from courseWork creation maps to the new
googleNotCourseTeacher reason instead of googleCourseNotFound.
Closes #1184
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🤖 autopilot status
Linked issue #1184. Maintained by autopilot (single writer); do not edit. |
|
🚀 Preview deployed: https://smalruby.jp/smalruby3-editor/topic/autopilot-1184/ |
… screen The share pre-flight verdict carried a groupId but nothing compared it, so a "not a course teacher" answer about the previously selected class disabled the share button for the class now shown (the probe is a network round trip and answers can arrive out of order, making it stick). The "ask the linking teacher" note was also rendered without the conditions the share button itself has, so an assignment already posted to Google Classroom showed its "open it" link next to a note telling the teacher to get it shared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
[Must] 事前判定の結果が「どのクラスについての答えか」を照合しておらず、別クラスの
const isGoogleShareBlocked = (access) => Boolean(access) && access.status === GOOGLE_COURSE_ACCESS.NOT_TEACHER;
再現確認(修正前): 対応: |
|
[Must] 共有ボタンが無い場面でも「共有できない理由」が出る(配信済み課題で「依頼してください」が出る)(修正済み: 16891fa)
その結果、すでに Google Classroom に配信済みの課題(ボタンが「Google Classroom で確認」リンク
と、**もう共有済みのものを「共有を依頼してください」**と案内する。配信済み課題を共同担当が見るのは通常運用なので頻度は低くない。 再現確認(修正前): 対応: |
|
[Question] 「コースが削除された」場合も Google は 削除された / アクセスできないコースにも 404 を返す。本 PR はその 404 を 2 箇所で「教師ではない」と断定している:
連携した先生本人のコースが GC 側で削除された場合(年度末の片付けなどで起こりうる)、その先生は:
となり、自分に依頼しろと言われて出口がない。旧 Google 側から 2 つを区別する手段は無い(どちらも 404 で隠される)ので、取りうる対応は文言で両方に触れることくらいだと思いますが、 参考: 「連携した先生=自分」のときだけ別文言にする案も、クライアントは自分が連携者かを判定できないので(上記フォールバックのため |
|
[Question] 同ファイルのクライアント側ファイルツリーには (この表は |
|
[FYI] <button data-testid="classroom-post-assignment" disabled={gcShareBlocked} title={gcShareBlockedText || null}>Chrome / Safari は無効化されたフォームコントロールでポインタイベントを発火しないため、ネイティブツールチップは基本的に表示されない。 |
|
[FYI] 事前判定は選択中クラスの GC 連携を張り替えても追従しない(コードは変更していません)
ただし倒れる向きは安全側:
実害は無さそうなので現状維持で良いと思いますが、依存が id だけである意図(コメント済み)の副作用として記録しておきます。 あわせて、 |
|
🤖 敵対的レビュー完了。指摘 6 件(Must 2 / Question 2 / FYI 2)。Must は修正済み。 Must 2 件(別クラスの事前判定結果の取り違え / 配信済み課題への「共有を依頼してください」表示)はどちらも再現を確認したうえで 16891fa で修正し、回帰テストを追加しました。Question は 1 件修正(docs の関数一覧)、1 件は設計判断が割れるため人間の判断に委ねます(GC コース削除時も "教師ではない" と断定する件)。 ローカル確認: |
|
[Must] 事前判定が「すでに Google トークンを持っている」ときしか走らないため、この Issue が対象にしている先生をちょうど素通りさせます。 prod で #1175 のログ強化を反映して実地検証した結果、状況が確定しました。
つまりこれは「今は権限が無いが、同意すれば通る」ではなく「何をしても通らない」ケースです。だからこそ、押させてから失敗を説明するのではなく、押す前に止める必要があります(既定をブロックにしても、正当な人を締め出すリスクは 2. の解除導線で回収できます)。
直してほしいこと既定を逆にし、事前判定を「ブロックの根拠」ではなく**「ブロックを解除する根拠」**として使ってください。
受け入れ条件の追加
[FYI] 今回の事例はメールドメインが違います(連携した先生 Refs #1175 |
…d it 共有できない先生の多くは「同意すれば通る」のではなく「何をしても通らない」 (別ワークスペースの個人アカウントは、そのコースの教師になれない)。事前判定を 「ブロックの根拠」ではなく「ブロックを解除する根拠」として使うよう既定を反転した。 - 連携した先生(googleClassroomLinkedBy。既存データは teacherSub)以外は既定で 共有ボタンを disabled にする。判定はサーバーが真偽値 linkedByMe を返す形にし、 クライアントでメール文字列を突き合わせない(大小文字・別名でぶれるため) - GC 共同教師の救済は明示的なオプトイン: ブロック表示の中の classroom-post-assignment-verify を押したときだけ同意を求めて判定し、teacher なら 共有を解禁する。クラスを開いただけでは同意ポップアップを出さない方針は維持 - 同意が得られない / notTeacher のときはエラーバナーを出さず、ブロック表示のまま 「確認できませんでした。◯◯ 先生に依頼してください」に差し替える - 連携した先生本人は判定を経ずに共有でき、その 404 は googleNotCourseTeacher では なく googleCourseNotFound(連携し直す案内)にする。自分に依頼させない - 文言はドメイン一致を判定条件にしない(外部ドメインの GC 共同教師はあり得る) Refs #1184 #1175 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
指摘 7 件(Must 1 / Question 2 / FYI 4 相当): 対応 4 件 / 対応不要 3 件。 対応した指摘[Must] 事前判定が「すでにトークンを持っている」ときしか走らず、この Issue の先生を素通りする → 既定を反転しました。
これにより、前回わたしが保留した [Question]「コース削除時も 受け入れ条件
対応不要と判断した指摘
テストと確認
|
|
🚀 stg にデプロイしました:
stg は共有環境です。この SHA が載っている前提で 動作確認してください。
|
Summary
Google Classroom への共有は 押した先生自身の Google アクセストークンで
courseWorkを作成するため、Google はその先生がそのコースの教師であることを要求する。Smalruby は共同担当にも共有ボタンを押させていたので、GC コースに入っていない共同担当が押すと生のgoogle ClassroomAPI error(実体は 404)になっていた。Smalruby 上の役割(owner / co-teacher)で決め打ちせず、GC コースの教師かどうかという実態で判定するようにした。同じコースに GC 共同教師として入っている先生は従来どおり共有できる。
Changes Made
サーバー (
infra/smalruby-classroom)GET /classroom-groups/{groupId}/google-course-accessを追加。caller のトークンでcourses/{courseId}/teachers/meを引き、200 →teacher/ 404 →notTeacher/ それ以外 →unknown(判定不能)を返す。linkedByEmailも返すgoogleClassroomLinkedBy({sub, email})に記録(クラス作成時・更新時の両方。解除時はクリア)。GET /classroom-groupsは表示用にgoogleClassroomLinkedByEmailを返す。既存データはクラス作成者(teacherSub)にフォールバック(メール不明)courseWork作成時の 404 を新しい reasongoogleNotCourseTeacherにマップ(GoogleAPIErrorにoperationを持たせた)。他の経路の 404 は従来どおりgoogleCourseNotFoundクライアント (
packages/scratch-gui)src/lib/classroom-google-share.js(新規・純粋):isGoogleShareBlocked()とgoogleShareBlockedMessage()notTeacherと判明したときだけdisabled。理由をclassroom-post-assignment-blockedに日本語で表示(「このクラスの Google Classroom は ◯◯ 先生が連携しています。共有は ◯◯ 先生に依頼してください。」/メール不明時は「連携した先生」と一般名詞)getCachedClassroomAccessToken()を追加)。判定のために同意ポップアップを出さない。判定できないときはボタンを無効化せず、失敗時にgoogleNotCourseTeacherの文言で同じ趣旨を出すja.js/ja-Hira.jsの両方に追加Test Coverage
infra/smalruby-classroom/lambda/tests/handler-google-course-access.test.ts(新規): reason マッピング(operation 付き)、buildGoogleClassroomLinkedBy/readGoogleClassroomLinkedByのフォールバック、classifyCourseTeacherFailure(404 のみnotTeacher)test/unit/lib/classroom-google-share.test.js(新規): 判定と文言test/unit/containers/classroom-error-utils.test.js:googleNotCourseTeacherの翻訳(googleCourseNotFoundと別文言・生のNOT_FOUNDを出さない)test/unit/components/teacher-class-detail.test.jsx: disabled / 理由表示 / unknown・未実行では無効化しないローカル結果: classroom lambda 23 suites 346 tests pass、scratch-gui の関連 unit test pass、
npm run lint(eslint + prettier)clean、tsc --noEmitclean。Docs
docs/classroom/architecture.md(ルート・reason・データモデル・判定方針の節)docs/classroom/ui-ux.md/docs/classroom/testing.md/docs/classroom/source-code.md.claude/rules/scratch-gui/e2e-test.mdの data-testid 一覧Deploy
サーバー変更は後方互換(ルート追加 + 任意フィールド追加)。stg へ載せるには PR に
deploy-stgラベルを付ける。prod への反映は人間が実施。Related Issues
Closes #1184
Refs #1175