Spike - RBAC AuthZ - Automatic Role Migration

Spike - RBAC AuthZ - Automatic Role Migration

Related ticket: https://github.com/openedx/openedx-platform/issues/38145

1. Overview

This Spike explores the implementation of an automatic role migration mechanism when the feature flag authz.enable_course_authoring changes.

The goal is to ensure that permission data remains consistent between the Legacy system (CourseAccessRole) and the new AuthZ system, without requiring manual administrator intervention.

The proposed solution uses:

  • Django Signals to detect changes in Waffle flags.

  • Celery Tasks to execute migrations asynchronously.

  • A tracking model (MigrationTask) for visibility and control.

2. Context

Currently, there are Django management commands that can be executed manually to perform the migration process at the course or organization level:

According to ADRs (#10 and #11):

  • Migration must occur automatically when the flag state changes (enabled/disabled)

  • The system must respect the scope:

    • Course

    • Organization

3. Problem

Currently:

  • Migration is manual.

  • There is no status tracking.

  • There is no concurrency protection.

  • There is no user feedback.

This can lead to:

  • Permission inconsistencies.

  • Race conditions.

  • Lack of observability.

4. Objectives

  • Define how to automatically trigger the migration.

  • Ensure that only one migration per scope runs at a time.

  • Design a status tracking mechanism.

  • Define how to inform the user about the result.

  • Validate that the process is safe, scalable, and decoupled.

5. Proposed Architecture

5.1 MigrationTask Model

A model is introduced for tracking:

class MigrationTask(models.Model): task_id = models.CharField(max_length=255, unique=True) migration_type = models.CharField(max_length=20) # forward / rollback scope_type = models.CharField(max_length=20) # course / org scope_key = models.CharField(max_length=255) status = models.CharField(max_length=20) # pending, running, completed, failed error_message = models.TextField(null=True, blank=True) metadata = models.JSONField(default=dict) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)

5.2 Trigger de Migración (Signals)

It is proposed to use Django signals (pre_save) on:

  • WaffleFlagCourseOverrideModel

  • WaffleFlagOrgOverrideModel

Flow:

  1. Detect change in authz.enable_course_authoring

  2. Detect transition:

    • False → True → Forward migration

    • True → False → Rollback

  3. Determine scope:

    • Course

    • Organization

  4. Trigger Celery task

5.3 Celery Tasks

Main task:

def migrate_course_authoring_async(migration_type, scope_type, scope_key)

Responsibilities:

  • Implement locking using Django cache/Redis.

  • Create a MigrationTask record and update its status during execution.

  • Execute migration using existing functions in engine/utils.py

  • Handle errors.

5.4 Locking Strategy

To avoid race conditions:

lock_key = f"authz_migration:{scope_type}:{scope_key}"
  • Implemented using cache.add()

  • Default TTL: 1 hour

Behavior:

  • If the lock exists → the new migration is ignored.

  • Prevents concurrent executions.

6. Execution Flow

  1. User changes the flag.

  2. Signal detects the change.

  3. Celery task is triggered.

  4. Task:

    1. Acquires lock

    2. Executes migration

    3. Updates status

  5. User checks status via Django Admin using the MigrationTask model

7. Handling Critical Cases

7.1 Race Conditions

Problem: Rapid flag changes (enable/disable)

Solution:

  • Lock per scope.

  • Async execution.

  • Ignore concurrent executions.

7.2 Migration Failures

  • Use of atomic transactions.

  • Do not delete data if migration fails.

  • Log the error in MigrationTask.

Open Questions

  1. Will migration run globally (Flag)?

    1. Automatic migration will not run for global flags. The main reason is that, depending on the instance, it could impact hundreds or thousands of courses, which introduces a high performance risk.

  2. Should the TTL be configurable?

  3. Should we create a REST API/command to check migration status?