Class: CurrentScope::RolesController

Inherits:
ApplicationController show all
Defined in:
app/controllers/current_scope/roles_controller.rb

Constant Summary collapse

ADD_LIMIT =

Who holds this role — the role-side complement to the subjects page. Org-wide holders (this role IS their one org-wide role) and per-record scoped holders, plus a capped list of subjects to add from here.

100

Instance Method Summary collapse

Instance Method Details

#createObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'app/controllers/current_scope/roles_controller.rb', line 12

def create
  @role = Role.new(role_params)
  saved = false
  Role.transaction do
    saved = @role.save
    # Fold the initial permission set into the create event — no separate
    # grid-diff event for a brand-new role.
    if saved
      Event.record!(event: "role.created", target: @role,
                    details: { name: @role.name, full_access: @role.full_access?,
                               permission_keys: @role.permission_keys })
    end
  end

  if saved
    redirect_to edit_role_path(@role), notice: "Role created."
  else
    render :new, status: :unprocessable_entity
  end
end

#destroyObject



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'app/controllers/current_scope/roles_controller.rb', line 98

def destroy
  refused = false

  Role.transaction do
    lock_full_access_console_state!
    role = Role.lock.find(params[:id])

    if would_lock_console_by_removing_role?(role)
      refused = true
    else
      # Snapshot WITHOUT polymorphic includes — includes(:subject)/:resource
      # can raise NameError for stale types at preload (members page avoids
      # this for the same reason). Resolve each row inside the helpers.
      org_removed = role.role_assignments.to_a
      scoped_revoked = role.scoped_role_assignments.to_a

      role.destroy!
      Event.record!(event: "role.deleted", target: role, details: { name: role.name })
      org_removed.each do |a|
        Event.record!(event: "org_role.removed", target: cascade_subject(a), details: { role: role.name })
      end
      scoped_revoked.each do |a|
        Event.record!(event: "scoped_role.revoked", target: cascade_subject(a),
                      details: { role: role.name, resource: cascade_resource_label(a) })
      end
    end
  end

  if refused
    redirect_to roles_path,
                alert: "Refusing to delete this full-access role — it is the last one held by any " \
                       "subject and would lock everyone out of this UI. Grant full access to " \
                       "another subject first, then retry."
    return
  end

  redirect_to roles_path, notice: "Role deleted."
end

#editObject



33
34
35
# File 'app/controllers/current_scope/roles_controller.rb', line 33

def edit
  @role = Role.find(params[:id])
end

#indexObject



3
4
5
6
# File 'app/controllers/current_scope/roles_controller.rb', line 3

def index
  # Includes for delete-confirm holder counts (cascade warning).
  @roles = Role.order(:name).includes(:role_assignments, :scoped_role_assignments)
end

#membersObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/controllers/current_scope/roles_controller.rb', line 42

def members
  @role = Role.find(params[:id])
  # No polymorphic includes: eager-loading a stale/renamed subject_type or
  # resource_type raises NameError. Lazy-load per row and label defensively
  # in the view (current_scope_holder_* helpers), like the audit ledger does.
  @org_holders = RoleAssignment.where(role: @role).to_a
  @scoped_holders = ScopedRoleAssignment.where(role: @role).to_a

  # Exclude via a subquery, not a plucked Ruby array, so a role with many
  # holders doesn't build a huge NOT IN bind list.
  held = RoleAssignment.where(role: @role, subject_type: subject_class.name).select(:subject_id)
  remaining = subject_class.where.not(id: held).order(:id)
  @candidates = remaining.limit(ADD_LIMIT).to_a
  @more_candidates = remaining.offset(ADD_LIMIT).exists?
end

#newObject



8
9
10
# File 'app/controllers/current_scope/roles_controller.rb', line 8

def new
  @role = Role.new
end

#updateObject



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
# File 'app/controllers/current_scope/roles_controller.rb', line 58

def update
  permitted = role_params
  previous_name = nil
  saved = false
  refused = false

  # Lock full-access roles + holders inside the write transaction so two
  # concurrent demotions of the last held full-access roles cannot both pass
  # a pre-transaction check and then both commit.
  Role.transaction do
    # Lock FA console state before the target role so concurrent demote/delete
    # of two FA roles cannot invert lock order (roles first, then assignments).
    lock_full_access_console_state!
    @role = Role.lock.find(params[:id])

    if demoting_would_lock_console?(@role, permitted)
      refused = true
    else
      previous_name = @role.name
      previous_full_access = @role.full_access?
      saved = @role.update(permitted)
      record_role_update(@role, previous_name, previous_full_access) if saved
    end
  end

  if refused
    redirect_to edit_role_path(@role),
                alert: "Refusing to remove full access — this is the last full-access role " \
                       "any subject holds and would lock everyone out of this UI. Grant " \
                       "full access to another subject first, then retry."
    return
  end

  if saved
    redirect_to roles_path, notice: "Role updated."
  else
    render :edit, status: :unprocessable_entity
  end
end