Skip to content

[GRDM-60922] Add permission checks and update token verification for admin endpoints - #796

Draft
ndnhat1 wants to merge 4 commits into
developfrom
feature/nii_grdm_202510_step2/institutional_admin_api_authorization
Draft

ndnhat1 wants to merge 4 commits into
developfrom
feature/nii_grdm_202510_step2/institutional_admin_api_authorization

Conversation

@ndnhat1

@ndnhat1 ndnhat1 commented Sep 16, 2026 •

Copy link
Copy Markdown

Purpose

Implement institutional admin API authorization and secure cron-triggered endpoints using HMAC-signed URLs instead of hardcoded tokens.

Changes

  • Authorization Control: Added superuser_required decorator and permission mixins to restrict sensitive admin endpoints (statistics, quota recalc, management commands) to superusers or institutional admins.
  • Signed URL Authentication: Replaced hardcoded token authentication with HMAC-signed, time-limited URLs for cron jobs.
  • Login Form Configuration: Linked the existing ENABLE_LOGIN_FORM setting to password-based authentication in the admin app. When ENABLE_LOGIN_FORM is False:
    • LoginView.form_valid() does not call authenticate().
    • The /password/ URLs are not registered.
    • /admin/login/ (the Django admin login) returns 404.
  • Superuser Check: Updated osf_superuser_check to reject disabled (is_active=False) and unregistered superusers.
  • View Security: Added PermissionRequiredMixin and get_object_or_404() to provider deletion views (collection, preprint, registration).

Deployment Notes

The following must be deployed together with this PR.

  • CRON_SIGNED_URL_SECRET must be set in local.py before deploying. In environments where DEV_MODE is False, the application does not start if it is unset or still has the default value (startup check in website/settings/init.py).
  • The cron jobs that call the two cron-triggered URLs (statistics gathering and external account update) must be updated to the new timestamp + signature URL format at the same time. The previous fixed-token URLs return 404 after this PR is deployed.

QA Notes

Documentation

Side Effects

  • In environments where ENABLE_LOGIN_FORM is disabled, the URLs under /password/ and /admin/login/ return 404.
  • The two URLs called from cron change to a timestamp + signature format. The previous fixed-token format returns 404.

Ticket

60922

@yacchin1205 yacchin1205 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントを入れました。ご確認ください。

Comment thread admin/base/urls.py

admin_urls = [
url(r'^$', views.home, name='home'),
url(r'^admin/', admin.site.urls),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ENABLE_LOGIN_FORM=False でも、ここで公開している /admin/login/ からパスワード認証が可能なように見えます。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yacchin1205

ご指摘ありがとうございます。2件とも、ご指摘のとおりでした。コメントごとに、対応・経緯・同種の漏れがないことの確認を記載します。修正完了後に、改めてレビュー依頼させていただきます。

対応

ENABLE_LOGIN_FORM が無効の場合、/admin/login/ を 404 とします(admin/base/urls.py で admin.site.urls より前に登録します)。学認でログイン済みのユーザは、引き続き Django 管理サイトを利用できます。UT・結合試験・総合テストに、404 となることの確認を追加します。

経緯

  • 本対応は、保守チケットで示された URL パスを対象に始めており、/admin/ 配下は対象に含まれていませんでした。
  • その後、パスワード関連の URL への対処として、パスワードによるログインを止めて学認ログインに統一する方針(ENABLE_LOGIN_FORM 連動)を採りました。この時点でパスワード認証の入口をすべて洗い出すべきところ、本リポジトリ内のログイン処理(LoginView)のみを対象としていました。Django 管理サイトのログインは Django 本体側で認証を呼び出すため、リポジトリ内の確認では対象から漏れていました。

同種の漏れがないことの確認

  • パスワード認証の入口:管理画面でパスワード認証を行うのは、LoginView と Django 管理サイトのログインの2か所のみです(管理画面の DRF の認証は SessionAuthentication のみ)。今回の修正で、ENABLE_LOGIN_FORM が無効の場合は両方とも使えなくなります。
  • Django 管理サイト配下のほかの URL:ログイン以外の URL は、Django の AdminSite.admin_view により、is_active かつ is_staff のユーザ以外はログイン画面へ転送されます。未ログインで利用できる URL は、ログイン以外にありません。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yacchin1205
対応が完了しましたので、改めて、レビュー依頼を投げさせていただきました。

Comment thread admin/base/utils.py

@wraps(view_func)
def wrapped_view(request, *args, **kwargs):
if not osf_superuser_check(request.user):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

無効化されたsuperuserを許可してしまうコードに見えます。他のコードと合わせるという点で、

def is_super_admin(self):
"""determine whether the user is super or not"""
user = self.request.user
if not (user.is_active and user.is_registered):
# invalid user
return False
if user.is_superuser:
return True
return False
のように、is_active と is_registered の確認をしておくのが手堅いかと思いました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yacchin1205

こちらも同様に修正完了後に、改めてレビュー依頼させていただきます。

対応

osf_superuser_check を、RdmPermissionMixin.is_super_admin と同じ条件(is_active と is_registered を確認)に揃えます。@superuser_required を使用する3つの view について、無効化・未登録の superuser が 403 となる確認を UT と結合試験に追加します。

同じファイルの osf_staff_check も is_active・is_registered を確認していませんが、使用箇所はトップ画面(admin/base/views.py の home)のみで、ログインユーザ本人の名前と各画面へのメニューを表示するだけのため、今回は変更しません。

経緯

  • superuser_required は、関数で書かれた view 向けに今回新設したデコレータです。判定(osf_superuser_check)は、同じファイルの既存の osf_staff_check と同じ形(ログイン済みかと権限フラグのみ)で作成しており、既存の RdmPermissionMixin.is_super_admin と条件を揃えていませんでした。
  • 試験も権限の種類ごとの許可・拒否のみを確認しており、無効化・未登録の状態を条件に含めていませんでした。

同種の漏れがないことの確認

  • 本 PR のほかのユーザ権限の判定は、既存の RdmPermissionMixin、または同じアプリの既存の画面と同じ PermissionRequiredMixin を使用しており、いずれも無効化されたユーザを拒否します。既存の判定と条件が異なる新設の判定は、superuser_required のみでした。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yacchin1205
対応が完了しましたので、改めて、レビュー依頼を投げさせていただきました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants