Class: SuperAuth::Editor

Inherits:
Object
  • Object
show all
Defined in:
lib/super_auth/editor.rb,
lib/super_auth/editor/cli.rb,
lib/super_auth/editor/seed.rb

Overview

A small Rack application that edits the authorization graph: five boxes of records (groups, roles and resources drawn as trees), client-side traversal, node and edge CRUD, and a Recompile button. Rails-free; it needs only SuperAuth.db to be connected and the tables to exist. Mount it as run SuperAuth::Editor (Rack) or mount SuperAuth::Editor => "/super_auth/editor" (Rails), or run super_auth-editor, which serves it on loopback.

It has no authentication of its own. Anyone who can reach it can rewrite the graph, so the host must put its own authentication in front of the mount. Two stdlib-only guards remain: writes must be application/json (a cross-origin browser cannot send that without a CORS preflight, which is never answered) and cross-site fetches are refused; hosts: additionally rejects any other Host header, the DNS-rebinding defence the executable turns on for loopback.

Edits change the graph, not runtime access: ByCurrentUser and the RLS policies read the compiled super_auth_authorizations table, so the UI shows its row count and offers POST /api/compile. A compile the models refuse (SuperAuth::Error, the wildcard guard) comes back as a 422 with the model's own message, like any other rejected write.

Defined Under Namespace

Modules: CLI, Seed

Constant Summary collapse

TYPES =
{
  "user" => :User, "group" => :Group, "role" => :Role,
  "permission" => :Permission, "resource" => :Resource,
}.freeze
COLUMNS =
{
  "user" => :user_id, "group" => :group_id, "role" => :role_id,
  "permission" => :permission_id, "resource" => :resource_id,
}.freeze
NESTED =
%w[group role resource].freeze
ALLOWED_PAIRS =

The pairs the path strategies read (see Edge.authorizations), unordered. The models also accept group->resource and role->resource rows, but no strategy reads them, so they would grant nothing.

[
  %w[user group], %w[user role], %w[user permission], %w[user resource],
  %w[group role], %w[group permission], %w[role permission], %w[permission resource],
].map(&:sort).freeze
EMPTY_EDGE =
{ user_id: nil, group_id: nil, role_id: nil, permission_id: nil, resource_id: nil }.freeze
INDEX_HTML =
File.read(File.join(__dir__, "editor", "index.html")).freeze
MAX_BODY =
64 * 1024
ID =
/\A\d+\z/
NAME_MAX =
255

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hosts: nil) ⇒ Editor

hosts: host names (port ignored) this app answers to; nil disables the check.



54
55
56
# File 'lib/super_auth/editor.rb', line 54

def initialize(hosts: nil)
  @hosts = hosts && hosts.map { |h| h.to_s.downcase }
end

Class Method Details

.call(env) ⇒ Object



49
50
51
# File 'lib/super_auth/editor.rb', line 49

def self.call(env)
  (@default ||= new).call(env)
end

Instance Method Details

#call(env) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/super_auth/editor.rb', line 58

def call(env)
  return forbidden("host not allowed") if @hosts && !@hosts.include?(host_of(env))

  begin
    SuperAuth.load unless defined?(SuperAuth::User)
  rescue Sequel::DatabaseError
    return json(503, error: "super_auth tables not found; run the migrations (super_auth-editor --migrate, or your application's)")
  end

  method = env["REQUEST_METHOD"]
  path = env["PATH_INFO"].to_s
  path = "/" if path.empty?
  if %w[POST DELETE].include?(method) && env["HTTP_SEC_FETCH_SITE"] == "cross-site"
    return forbidden("cross-site requests are not accepted")
  end

  route(method, path, env)
rescue SuperAuth::Error => e
  json(422, error: e.message)
rescue Sequel::Error
  json(422, error: "the database rejected the change")
end