Class: Verifica::Acl

Inherits:
Object
  • Object
show all
Defined in:
lib/verifica/acl.rb

Overview

Access Control List (ACL)

Access Control List consists of Access Control Entities (ACEs) and defines which actions are allowed or denied for particular Security Identifiers (SIDs).

ACL is typically associated with a resource (e.g. Post, Comment, Order) and specifies which users (or external services, or API clients) are allowed to do what actions on the given resource.

See Also:

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(aces) ⇒ Acl

Note:

Use build instead of this constructor directly.

Creates a new Access Control List with immutable state.

Parameters:

  • aces (Array<Ace>)

    list of Access Control Entries


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/verifica/acl.rb', line 48

def initialize(aces)
  @aces = Set.new(aces).freeze
  @allow_deny_by_action = prepare_index.freeze
  @allowed_actions = Set.new
  @allow_deny_by_action.each do |action, allow_deny|
    @allowed_actions.add(action) unless allow_deny[:allowed_sids].empty?

    allow_deny[:allowed_sids].freeze
    allow_deny[:denied_sids].freeze
    allow_deny.freeze
  end

  @allowed_actions.freeze
  freeze
end

Class Method Details

.build {|builder| ... } ⇒ Acl

Creates a new Verifica::AclBuilder and yields it to the given block.

Examples:

acl = Verifica::Acl.build do |acl|
  acl.allow "anonymous", [:read]
  acl.allow "authenticated", [:read, :comment]
  acl.deny "country:US", [:read, :comment]
end

Yields:

  • (builder)

Returns:

  • (Acl)

    Access Control List created by builder


33
34
35
36
37
# File 'lib/verifica/acl.rb', line 33

def self.build
  builder = AclBuilder.new
  yield builder
  builder.build
end

Instance Method Details

#==(other) ⇒ Object


186
187
188
# File 'lib/verifica/acl.rb', line 186

def ==(other)
  eql?(other)
end

#action_allowed?(action, sids) ⇒ Boolean

Checks whether the action is allowed for given Security Identifiers. For action to be allowed all 3 conditions should be met:

  • ACL and SIDs are not empty

  • ACL contains at least one entry that allow given action for any of the SIDs

  • ACL contains no entries that deny given action for any of the SIDs

Parameters:

  • action (Symbol, String)

    action to check

  • sids (Array<String>, Set<String>)

    list of Security Identifiers to match for

Returns:

  • (Boolean)

    true if action is allowed


77
78
79
80
81
82
83
84
85
86
87
# File 'lib/verifica/acl.rb', line 77

def action_allowed?(action, sids)
  return false if empty? || sids.empty?

  action = action.to_sym
  allow_deny = @allow_deny_by_action[action]

  return false if allow_deny.nil? || !@allowed_actions.include?(action)

  sids = sids.to_set
  allow_deny[:allowed_sids].intersect?(sids) && !allow_deny[:denied_sids].intersect?(sids)
end

#action_denied?(action, sids) ⇒ Boolean

The opposite of #action_allowed?

Returns:

  • (Boolean)

92
93
94
# File 'lib/verifica/acl.rb', line 92

def action_denied?(action, sids)
  !action_allowed?(action, sids)
end

#allowed_actions(sids) ⇒ Array<Symbol>

Returns array of actions allowed for given Security Identifiers or empty array if none.

Parameters:

  • sids (Array<String>, Set<String>)

    list of Security Identifiers to match for

Returns:

  • (Array<Symbol>)

    array of actions allowed for given Security Identifiers or empty array if none


127
128
129
130
131
# File 'lib/verifica/acl.rb', line 127

def allowed_actions(sids)
  return EMPTY_ARRAY if sids.empty?

  @allowed_actions.select { |action| action_allowed?(action, sids) }
end

#allowed_sids(action) ⇒ Array<String>

Note:

Checking allowed SIDs isn't enough to determine whether the action is allowed. You need to always check #denied_sids as well.

Returns array of Security Identifiers allowed for a given action or empty array if none.

Parameters:

  • action (Symbol, String)

    action to check

Returns:

  • (Array<String>)

    array of Security Identifiers allowed for a given action or empty array if none


104
105
106
107
# File 'lib/verifica/acl.rb', line 104

def allowed_sids(action)
  sids = @allow_deny_by_action.dig(action.to_sym, :allowed_sids)
  sids.nil? ? EMPTY_ARRAY : sids.to_a
end

#build {|builder| ... } ⇒ Acl

Creates a new Verifica::AclBuilder, adds existing entries to it and yields it to the given block. Use this method to extend an existing ACL with additional entries

Examples:

base_acl = Verifica::Acl.build do |acl|
  acl.allow "superuser", [:read, :write, :delete]
end

extended_acl = base_acl.build do |acl|
  acl.allow "anonymous", [:read]
  acl.allow "authenticated", [:read, :comment]
end

Yields:

  • (builder)

Returns:

  • (Acl)

    new Access Control List created by builder


149
150
151
152
153
# File 'lib/verifica/acl.rb', line 149

def build
  builder = AclBuilder.new(to_a)
  yield builder
  builder.build
end

#denied_sids(action) ⇒ Array<String>

Note:

Checking denied SIDs isn't enough to determine whether the action is allowed. You need to always check #allowed_sids as well.

Returns array of Security Identifiers denied for a given action or empty array if none.

Parameters:

  • action (Symbol, String)

    action to check

Returns:

  • (Array<String>)

    array of Security Identifiers denied for a given action or empty array if none


117
118
119
120
# File 'lib/verifica/acl.rb', line 117

def denied_sids(action)
  sids = @allow_deny_by_action.dig(action.to_sym, :denied_sids)
  sids.nil? ? EMPTY_ARRAY : sids.to_a
end

#empty?Boolean

Returns true if there are no entries in self.

Returns:

  • (Boolean)

    true if there are no entries in self


170
171
172
# File 'lib/verifica/acl.rb', line 170

def empty?
  @aces.empty?
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)

190
191
192
193
# File 'lib/verifica/acl.rb', line 190

def eql?(other)
  self.class == other.class &&
    @aces == other.aces
end

#hashObject


195
196
197
# File 'lib/verifica/acl.rb', line 195

def hash
  [self.class, @aces].hash
end

#lengthInteger Also known as: size

Returns the count of entries in self.

Returns:

  • (Integer)

    the count of entries in self


177
178
179
# File 'lib/verifica/acl.rb', line 177

def length
  @aces.length
end

#to_aArray<Ace>

Returns a new array representing self.

Examples:

acl = Verifica::Acl.build { |acl| acl.allow "root", [:read, :write] }
acl.to_a.map(:to_h)
# => [{:sid=>"root", :action=>:read, :allow=>true}, {:sid=>"root", :action=>:write, :allow=>true}]

Returns:

  • (Array<Ace>)

    a new array representing self


163
164
165
# File 'lib/verifica/acl.rb', line 163

def to_a
  @aces.to_a
end

#to_sObject


182
183
184
# File 'lib/verifica/acl.rb', line 182

def to_s
  @aces.map(&:to_h).to_s
end