Class: Rhino::PermissionsMigrator
- Inherits:
-
Object
- Object
- Rhino::PermissionsMigrator
- Defined in:
- lib/rhino/permissions_migrator.rb
Overview
Lift per-user permissions into the shared org role layer.
For each (organization, role) group, the literal intersection of every user’s ‘user_roles.permissions` becomes the `org_role_permissions` row (the shared role layer). Each user’s row is then reduced to only its delta (‘granted_permissions = permissions − roleLayer`) and its legacy `permissions` is cleared. Effective permissions are preserved exactly (the intersection is a subset of every user’s set, so nothing is gained or lost).
Safe & idempotent:
- Dry-run by default; pass apply: true to write.
- Groups that already have an org_role_permissions row are skipped.
- After a run the legacy permissions are empty, so a second run is a no-op.
- Non-tenant (NULL organization) rows are left untouched.
Defined Under Namespace
Classes: Result
Class Method Summary collapse
Instance Method Summary collapse
Class Method Details
.call(apply: false) ⇒ Object
23 24 25 |
# File 'lib/rhino/permissions_migrator.rb', line 23 def self.call(apply: false) new.call(apply: apply) end |
Instance Method Details
#call(apply: false) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/rhino/permissions_migrator.rb', line 27 def call(apply: false) conn = ActiveRecord::Base.connection unless conn.data_source_exists?("user_roles") && conn.data_source_exists?("org_role_permissions") raise "Required tables (user_roles, org_role_permissions) are missing. Run migrations first." end groups = conn.select_all( "SELECT DISTINCT organization_id, role_id FROM user_roles " \ "WHERE organization_id IS NOT NULL AND role_id IS NOT NULL" ) groups_migrated = 0 rows_reduced = 0 skipped_existing = 0 lines = [] groups.each do |g| org_id = g["organization_id"] role_id = g["role_id"] rows = conn.select_all( ActiveRecord::Base.sanitize_sql_array( ["SELECT id, permissions, granted_permissions FROM user_roles " \ "WHERE organization_id = ? AND role_id = ?", org_id, role_id] ) ).to_a with_legacy = rows.select { |r| decode(r["permissions"]).any? } next if with_legacy.empty? existing = conn.select_value( ActiveRecord::Base.sanitize_sql_array( ["SELECT 1 FROM org_role_permissions WHERE organization_id = ? AND role_id = ? LIMIT 1", org_id, role_id] ) ) if existing skipped_existing += 1 next end sets = with_legacy.map { |r| decode(r["permissions"]) } role_layer = sets.reduce { |acc, s| acc & s } || [] lines << "org=#{org_id} role=#{role_id} → role layer [#{role_layer.join(', ')}] (#{with_legacy.size} user rows)" if apply now = Time.now.utc conn.execute( ActiveRecord::Base.sanitize_sql_array( ["INSERT INTO org_role_permissions (organization_id, role_id, permissions, created_at, updated_at) " \ "VALUES (?, ?, ?, ?, ?)", org_id, role_id, JSON.generate(role_layer), now, now] ) ) with_legacy.each do |r| legacy = decode(r["permissions"]) grants = decode(r["granted_permissions"]) delta = ((legacy - role_layer) + grants).uniq conn.execute( ActiveRecord::Base.sanitize_sql_array( ["UPDATE user_roles SET permissions = ?, granted_permissions = ?, updated_at = ? WHERE id = ?", JSON.generate([]), JSON.generate(delta), now, r["id"]] ) ) end end groups_migrated += 1 rows_reduced += with_legacy.size end Result.new( groups_migrated: groups_migrated, rows_reduced: rows_reduced, skipped_existing: skipped_existing, lines: lines ) end |