checkpoint 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +15 -0
  3. data/.travis.yml +8 -0
  4. data/README.md +6 -0
  5. data/db/migrations/{1_create_permits.rb → 1_create_grants.rb} +1 -1
  6. data/docs/index.rst +6 -1
  7. data/lib/checkpoint/agent.rb +32 -31
  8. data/lib/checkpoint/agent/resolver.rb +45 -15
  9. data/lib/checkpoint/agent/token.rb +8 -3
  10. data/lib/checkpoint/authority.rb +126 -14
  11. data/lib/checkpoint/credential.rb +21 -8
  12. data/lib/checkpoint/credential/permission.rb +6 -5
  13. data/lib/checkpoint/credential/resolver.rb +63 -62
  14. data/lib/checkpoint/credential/role.rb +2 -3
  15. data/lib/checkpoint/credential/role_map_resolver.rb +65 -0
  16. data/lib/checkpoint/credential/token.rb +7 -2
  17. data/lib/checkpoint/db.rb +8 -1
  18. data/lib/checkpoint/db/cartesian_select.rb +71 -0
  19. data/lib/checkpoint/db/{permit.rb → grant.rb} +4 -4
  20. data/lib/checkpoint/db/params.rb +36 -0
  21. data/lib/checkpoint/db/query/ac.rb +47 -0
  22. data/lib/checkpoint/db/query/acr.rb +54 -0
  23. data/lib/checkpoint/db/query/ar.rb +47 -0
  24. data/lib/checkpoint/db/query/cr.rb +47 -0
  25. data/lib/checkpoint/grants.rb +116 -0
  26. data/lib/checkpoint/query/role_granted.rb +1 -1
  27. data/lib/checkpoint/resource.rb +16 -19
  28. data/lib/checkpoint/resource/all_of_any_type.rb +0 -8
  29. data/lib/checkpoint/resource/all_of_type.rb +0 -22
  30. data/lib/checkpoint/resource/resolver.rb +34 -11
  31. data/lib/checkpoint/resource/token.rb +7 -2
  32. data/lib/checkpoint/version.rb +1 -1
  33. metadata +12 -7
  34. data/docs/authentication.rst +0 -18
  35. data/lib/checkpoint/permission_mapper.rb +0 -29
  36. data/lib/checkpoint/permits.rb +0 -133
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'checkpoint/credential/resolver'
4
+ require 'checkpoint/credential/role_map_resolver'
4
5
  require 'checkpoint/credential/role'
5
6
  require 'checkpoint/credential/permission'
6
7
  require 'checkpoint/credential/token'
7
- require 'checkpoint/permission_mapper'
8
8
 
9
9
  module Checkpoint
10
10
  # A Credential is the permission to take a particular action, or any
@@ -13,8 +13,8 @@ module Checkpoint
13
13
  #
14
14
  # Credentials are abstract; that is, they are not attached to a particular
15
15
  # actor or resource to be acted upon. A credential can be granted to an
16
- # {Agent}, optionally applying to a particular resource, by way of a Permit.
17
- # In other words, a credential can be likened to a class, while a permit can
16
+ # {Agent}, optionally applying to a particular resource, by way of a Grant.
17
+ # In other words, a credential can be likened to a class, while a grant can
18
18
  # be likened to an instance of that class, bound to a given agent and
19
19
  # possibly bound to a {Resource}.
20
20
  class Credential
@@ -47,23 +47,36 @@ module Checkpoint
47
47
  # This is an extension mechanism for application authors needing to
48
48
  # implement hierarchical or virtual credentials and wanting to do so in
49
49
  # an object-oriented way. The default implementation is to simply return
50
- # the credential itself in an array but, for example, an a custom
50
+ # the credential itself in an array but, for example, a custom
51
51
  # permission type could provide for aliasing by including itself and
52
52
  # another instance for the synonym. Another example is modeling permissions
53
53
  # granted by particular roles; this might be static, as defined in the
54
54
  # source files, or dynamic, as impacted by configuration or runtime data.
55
55
  #
56
- # As an alternative, these rules could be implemented under a
57
- # {PermissionMapper} in an application that prefers to model its credentials
58
- # as strings or symbols, rather than more specialized objects.
56
+ # As an alternative, these rules could be implemented by using the rather
57
+ # straightforward {RoleMapResolver} or a custom {Credential::Resolver}.
59
58
  #
60
- # @see Checkpoint::PermissionMapper
61
59
  # @return [Array<Credential>] the expanded list of credentials that would
62
60
  # grant this one
63
61
  def granted_by
64
62
  [self]
65
63
  end
66
64
 
65
+ # Convert this object to a Credential.
66
+ #
67
+ # For Checkpoint-supplied Credential types, this is an identity operation,
68
+ # but it allows consistent handling of the built-in types and
69
+ # application-supplied types that will either implement this interface or
70
+ # convert themselves to a built-in type. This removes the requirement to
71
+ # extend Checkpoint types and, in combination with `#granted_by`, allows
72
+ # design of an object-oriented permission model that can interoperate
73
+ # seamlessly with the Checkpoint constructs.
74
+ #
75
+ # @return [Credential] this credential
76
+ def to_credential
77
+ self
78
+ end
79
+
67
80
  # @return [Token] a token for this credential
68
81
  def token
69
82
  @token ||= Token.new(type, id)
@@ -11,11 +11,12 @@ module Checkpoint
11
11
  # action is permitted for a user. However, Permission could be extended or
12
12
  # modified to implement aliasing or hierarchy, for example.
13
13
  #
14
- # More likely, though, is advising the resolution of Permissions through a
15
- # {Checkpoint::PermissionMapper} or implementing a custom
16
- # {Checkpoint::Credential::Resolver}. Subclassing or monkey-patching Permission
17
- # should only be necessary if the application needs to extend the actual
18
- # behavior of the Permission objects, rather than just which ones are resolved.
14
+ # More likely, though, is advising the resolution of Permissions with a
15
+ # role map and {Checkpoint::Credential::RoleMapResolver} or implementing a
16
+ # custom {Checkpoint::Credential::Resolver}. Subclassing or monkey-patching
17
+ # Permission should only be necessary if the application needs to extend
18
+ # the actual behavior of the Permission objects, rather than just which
19
+ # ones are resolved.
19
20
  class Permission < Credential
20
21
  TYPE = 'permission'
21
22
 
@@ -1,87 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'checkpoint/permission_mapper'
4
-
5
3
  module Checkpoint
6
4
  class Credential
7
- # A Credential Resolver takes a concrete action name and resolves it into any
8
- # {Credential}s that would permit the action.
5
+ # A Credential Resolver is the bridge between application action names
6
+ # and {Credential}s that would permit those actions.
9
7
  #
10
8
  # Checkpoint makes no particular demand on the credential model for an
11
- # application, but offers a useful default implementation supporting
9
+ # application, but offers two useful default implementations supporting
12
10
  # permissions and roles. There are no default rules in Checkpoint as to which
13
11
  # permissions or roles exist and, therefore, it has no default mapping of
14
12
  # roles to permissions.
15
13
  #
16
- # This default resolver can be advised about an application model using roles
17
- # and permissions customized by using one or both of two extension points:
14
+ # This default resolver is useful for applications that only deal with
15
+ # permissions or model credentials in an object-oriented way (explained
16
+ # below). The {RoleMapResolver} supports a basic mapping pattern between
17
+ # named roles and permissions those roles would grant.
18
+ #
19
+ # More sophisticated mappings, such as those reading a configuration file
20
+ # or with dynamic roles and permissions defined in a database, can be
21
+ # implemented in a custom resolver.
18
22
  #
19
- # 1. Supplying a {PermissionMapper} gives a way to map action names to any
20
- # "larger" permissions (e.g., "manage" being a shorthand for all CRUD
21
- # operations on a Resource type) or roles that would grant a given
22
- # permission. This affords a rather straightforward mapping of strings
23
- # or symbols, short of building customized Credential types.
23
+ # Using this default resolver, it is possible to implement your own
24
+ # {Credential} types to model an application's credentials in an
25
+ # object-oriented way. If the resolver receives a {Credential} (rather than
26
+ # a string or symbol), it will call `#granted_by` on it to expand it. The
27
+ # Credential should be sure to include itself in the array it returns
28
+ # unless it is virtual and should never be considered as granted directly.
24
29
  #
25
- # 2. Implementing your own {Credential} types gives a way to model an
26
- # application's credentials in an object-oriented way. If the resolver
27
- # receives a {Credential} (rather than a string or symbol), it will call
28
- # `#granted_by` on it to expand it. The Credential should be sure to
29
- # include itself in the array it returns unless it is virtual and should
30
- # never be considered as granted directly.
30
+ # An example of this type of modeling could be implementing named action
31
+ # classes that have information like labels and descriptions for display
32
+ # purposes in a permission management interface or messages when a user
33
+ # does not have sufficient permission to take that action. There might also
34
+ # be some inheritance such that granting something like "manage" would be a
35
+ # shorthand for all CRUD operations on a Resource type.
31
36
  #
37
+ # Another example of a custom Credential type could be a license that would
38
+ # be an application object in its own right, carrying information such as
39
+ # the licensor and expiration date. By implementing the Credential
40
+ # interface, a license could be granted directly with the Checkpoint
41
+ # {Authority} and enforced at the policies.
42
+ #
43
+ # @see {RoleMapResolver}
32
44
  class Resolver
33
- def initialize(permission_mapper: PermissionMapper.new)
34
- @permission_mapper = permission_mapper
35
- end
36
-
37
- # Resolve an action into all {Credential}s that would permit it.
45
+ # Expand an action into all {Credential}s that would permit it.
38
46
  #
39
- # When supplied a string or symbol, we call `permissions_for` and
40
- # `roles_granting` on the {PermissionMapper} creating a {Permission} or
41
- # {Role} for every result, returning them all in an array.
47
+ # Expansion first converts the action to a Credential, and then calls
48
+ # `#granted_by` on it.
42
49
  #
43
- # When supplied a Credential, we call `#granted_by` on it and bypass the
44
- # PermissionMapper. More precisely, we only check that the object responds to
45
- # `#granted_by?`, but it would generally be a Credential subclass. The
46
- # Credential should return an array, but we ensure the return type by
47
- # wrapping and flattening.
50
+ # Note that the parameter name is `action`, though it can accept a
51
+ # Credential. This is to promote the most common and recommended model:
52
+ # authorizing based on named application actions. However, the
53
+ # polymorphic and hierarchical nature of credentials means that there can
54
+ # be cases where expanding something like a {Role} is intentional. As an
55
+ # example, an administrative interface for managing roles granted to
56
+ # users might need to expand the roles to show inheritance, rather than
57
+ # checking whether a given user would be permitted to take some action.
48
58
  #
49
- # Note that the parameter name to `resolve` is `action`. This isn't a perfect
50
- # name, but credentials are polymorphic such a way that there really is no
51
- # better application-side term (cf. actor -> Agent, entity -> Resource). It
52
- # would be something like `action_or_role`, `permission_or_role`, or a generic
53
- # `credential`. Part of the naming intent here was to distinguish from the
54
- # action and the ability to perform it. This inheritance relationship
55
- # permissions and roles are both credential types is a distinguishing feature
56
- # of Checkpoint, as opposed to models that treat permissions and roles as
57
- # distinct concepts that must be granted in very different ways. A better
58
- # name for this parameter may emerge over time, but it seems unlikely. The
59
- # name `action` was selected because the most common and appropriate concrete
60
- # thing to look for is a permission to take a named application action.
59
+ # @param action [String|Symbol|Credential] the action name or Credential
60
+ # to expand
61
+ # @return [Array<Credential>] the set of Credentials, any of which would
62
+ # allow the action
63
+ def expand(action)
64
+ convert(action).granted_by
65
+ end
66
+
67
+ # Convert an action to a Credential.
68
+ #
69
+ # This conversion is basic, assuming that actions should convert directly
70
+ # to permissions. For example, if `:read` or `'read'` is passed, a
71
+ # {Credential::Permission} with id `'read'` is returned. If the action
72
+ # implements `#to_credential`, that will be called and returned; the
73
+ # object must either extend {Credential} or implement its public methods.
61
74
  #
62
75
  # @param action [String|Symbol|Credential] the action name or Credential
63
- # to expand into any Credential that would grant it.
64
- def resolve(action)
65
- if action.respond_to?(:granted_by)
66
- [action.granted_by].flatten
76
+ # to convert
77
+ # @return Credential a Credential object that would specifically allow
78
+ # the action supplied
79
+ def convert(action)
80
+ if action.respond_to?(:to_credential)
81
+ action.to_credential
67
82
  else
68
- permissions_for(action) + roles_granting(action)
83
+ Permission.new(action)
69
84
  end
70
85
  end
71
-
72
- private
73
-
74
- def permissions_for(action)
75
- perms = permission_mapper.permissions_for(action)
76
- perms.map { |perm| Credential::Permission.new(perm) }
77
- end
78
-
79
- def roles_granting(action)
80
- roles = permission_mapper.roles_granting(action)
81
- roles.map { |role| Credential::Role.new(role) }
82
- end
83
-
84
- attr_reader :permission_mapper
85
86
  end
86
87
  end
87
88
  end
@@ -8,10 +8,9 @@ module Checkpoint
8
8
  # The most common use from outside Checkpoint will be by way of
9
9
  # {Checkpoint::Query::RoleGranted}, which will ask whether a given named
10
10
  # role is granted for a user. However, Role could be extended or modified
11
- # to implement aliasing or hierarchy, for example
11
+ # to implement aliasing or hierarchy, for example.
12
12
  #
13
- # More likely, though, is advising the resolution of Roles through a
14
- # {Checkpoint::PermissionMapper} or implementing a custom
13
+ # More likely, though, is implementing a custom
15
14
  # {Checkpoint::Credential::Resolver}. Subclassing or monkey-patching Role
16
15
  # should only be necessary if the application needs to extend the actual
17
16
  # behavior of the Role objects, rather than just which ones are resolved.
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Checkpoint::Credential
4
+ # Credential Resolver that supports a basic role map model.
5
+ #
6
+ # The role map should be a hash containing all of the roles and each key
7
+ # should be an array of the permissions that role would grant. For example:
8
+ #
9
+ # ```
10
+ # {
11
+ # admin: [:read, :create, :edit, :delete],
12
+ # guest: [:read]
13
+ # }
14
+ # ```
15
+ #
16
+ # Note that this example is not a recommendation of how to model an
17
+ # application's permissions; it is only to show the expected format of the
18
+ # hash and that there is no inheritance of permissions between roles (:read
19
+ # is included in both roles). Any more sophisticated rules should be
20
+ # implemented in a custom Resolver, or custom Credential types.
21
+ #
22
+ # Actions convert to Permissions according to the base {Resolver} and expand
23
+ # according to the map.
24
+ class RoleMapResolver < Resolver
25
+ attr_reader :role_map, :permission_map
26
+
27
+ def initialize(role_map)
28
+ @role_map = role_map
29
+ @permission_map = invert_role_map
30
+ end
31
+
32
+ # Expand an action name into the matching permission and any roles that
33
+ # would grant it.
34
+ #
35
+ # @return [Array<Credential>]
36
+ def expand(action)
37
+ permissions_for(action) + roles_granting(action)
38
+ end
39
+
40
+ private
41
+
42
+ def permissions_for(action)
43
+ [Permission.new(action)]
44
+ end
45
+
46
+ def roles_granting(action)
47
+ if permission_map.key?(action)
48
+ permission_map[action].map {|role| Role.new(role) }
49
+ else
50
+ []
51
+ end
52
+ end
53
+
54
+ def invert_role_map
55
+ {}.tap do |hash|
56
+ role_map.each do |role, permissions|
57
+ permissions.each do |permission|
58
+ hash[permission] ||= []
59
+ hash[permission] << role
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -3,9 +3,9 @@
3
3
  module Checkpoint
4
4
  class Credential
5
5
  # A Credential::Token is an identifier object for a Credential. It includes
6
- # a type and an identifier. A {Permit} can be granted for a Token. Concrete
6
+ # a type and an identifier. A {Grant} can be created for a Token. Concrete
7
7
  # actions are resolved into a number of credentials, and those credentials'
8
- # tokens will be checked for matching permits.
8
+ # tokens will be checked for matching grants.
9
9
  class Token
10
10
  attr_reader :type, :id
11
11
 
@@ -44,6 +44,11 @@ module Checkpoint
44
44
  other.is_a?(Token) && type == other.type && id == other.id
45
45
  end
46
46
 
47
+ # @return [Integer] hash code based on to_s
48
+ def hash
49
+ to_s.hash
50
+ end
51
+
47
52
  alias == eql?
48
53
  alias inspect uri
49
54
  end
@@ -4,6 +4,13 @@ require 'ostruct'
4
4
  require 'logger'
5
5
  require 'yaml'
6
6
 
7
+ require_relative 'db/cartesian_select'
8
+ require_relative 'db/params'
9
+ require_relative 'db/query/acr'
10
+ require_relative 'db/query/ac'
11
+ require_relative 'db/query/ar'
12
+ require_relative 'db/query/cr'
13
+
7
14
  module Checkpoint
8
15
  # Module for everything related to the Checkpoint database.
9
16
  module DB
@@ -110,7 +117,7 @@ module Checkpoint
110
117
 
111
118
  def model_files
112
119
  [
113
- 'db/permit'
120
+ 'db/grant'
114
121
  ]
115
122
  end
116
123
 
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkpoint
4
+ module DB
5
+ # Helper for querying by cross-products across sets of parameters,
6
+ # especially for grants.
7
+ #
8
+ # This class is called CartesianSelect because the logical search space is
9
+ # the Cartesian product, for example, of agents X credentials X resources.
10
+ # All grants in that space would be selected.
11
+ #
12
+ # This is a base class to support convenient variations for searching in
13
+ # different scenarios. It is unlikely to be very useful in its own right,
14
+ # but provides structure for specific subclasses. For example, {Query::ACR}
15
+ # searches for grants when agents, credentials, and resources are all
16
+ # known, as when checking authorization. When seeking to list agents that
17
+ # could take a given action on a resource, {Query::CR} would be useful.
18
+ #
19
+ # Subclasses should extends the conditions and parameters methods to supply
20
+ # the placeholders and matching values. The {Params} class is helpful for
21
+ # that purpose.
22
+ #
23
+ # The queries are ultimately implemented with an IN clause for each key in
24
+ # the conditions with binding expressions in the way Sequel expects them.
25
+ class CartesianSelect
26
+ attr_reader :scope
27
+
28
+ def initialize(scope: Grant)
29
+ @scope = scope
30
+ end
31
+
32
+ def query
33
+ scope.where(conditions)
34
+ end
35
+
36
+ def all
37
+ exec(:select)
38
+ end
39
+
40
+ def first
41
+ exec(:first)
42
+ end
43
+
44
+ def delete
45
+ exec(:delete)
46
+ end
47
+
48
+ def conditions
49
+ {
50
+ zone_id: :$zone_id
51
+ }
52
+ end
53
+
54
+ def parameters
55
+ {
56
+ zone_id: Grant.default_zone
57
+ }
58
+ end
59
+
60
+ private
61
+
62
+ def exec(mode)
63
+ query.call(mode, parameters)
64
+ end
65
+
66
+ def tokenize(collection)
67
+ [collection].flatten.map(&:token)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Checkpoint
4
4
  module DB
5
- # Sequel model for permits
6
- class Permit < Sequel::Model(DB.db)
7
- # Instantiate a Permit from the constituent domain objects (agent,
5
+ # Sequel model for grants
6
+ class Grant < Sequel::Model(DB.db)
7
+ # Instantiate a Grant from the constituent domain objects (agent,
8
8
  # resource, credential).
9
- def self.from(agent, credential, resource, zone: 'system')
9
+ def self.from(agent, credential, resource, zone: default_zone)
10
10
  new(
11
11
  agent_type: agent.type, agent_id: agent.id, agent_token: agent.token,
12
12
  credential_type: credential.type, credential_id: credential.id, credential_token: credential.token,