checkpoint 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -0
- data/.travis.yml +8 -0
- data/README.md +6 -0
- data/db/migrations/{1_create_permits.rb → 1_create_grants.rb} +1 -1
- data/docs/index.rst +6 -1
- data/lib/checkpoint/agent.rb +32 -31
- data/lib/checkpoint/agent/resolver.rb +45 -15
- data/lib/checkpoint/agent/token.rb +8 -3
- data/lib/checkpoint/authority.rb +126 -14
- data/lib/checkpoint/credential.rb +21 -8
- data/lib/checkpoint/credential/permission.rb +6 -5
- data/lib/checkpoint/credential/resolver.rb +63 -62
- data/lib/checkpoint/credential/role.rb +2 -3
- data/lib/checkpoint/credential/role_map_resolver.rb +65 -0
- data/lib/checkpoint/credential/token.rb +7 -2
- data/lib/checkpoint/db.rb +8 -1
- data/lib/checkpoint/db/cartesian_select.rb +71 -0
- data/lib/checkpoint/db/{permit.rb → grant.rb} +4 -4
- data/lib/checkpoint/db/params.rb +36 -0
- data/lib/checkpoint/db/query/ac.rb +47 -0
- data/lib/checkpoint/db/query/acr.rb +54 -0
- data/lib/checkpoint/db/query/ar.rb +47 -0
- data/lib/checkpoint/db/query/cr.rb +47 -0
- data/lib/checkpoint/grants.rb +116 -0
- data/lib/checkpoint/query/role_granted.rb +1 -1
- data/lib/checkpoint/resource.rb +16 -19
- data/lib/checkpoint/resource/all_of_any_type.rb +0 -8
- data/lib/checkpoint/resource/all_of_type.rb +0 -22
- data/lib/checkpoint/resource/resolver.rb +34 -11
- data/lib/checkpoint/resource/token.rb +7 -2
- data/lib/checkpoint/version.rb +1 -1
- metadata +12 -7
- data/docs/authentication.rst +0 -18
- data/lib/checkpoint/permission_mapper.rb +0 -29
- 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
|
17
|
-
# In other words, a credential can be likened to a class, while a
|
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,
|
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
|
57
|
-
# {
|
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
|
15
|
-
# {Checkpoint::
|
16
|
-
# {Checkpoint::Credential::Resolver}. Subclassing or monkey-patching
|
17
|
-
# should only be necessary if the application needs to extend
|
18
|
-
# behavior of the Permission objects, rather than just which
|
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
|
8
|
-
# {Credential}s that would permit
|
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
|
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
|
17
|
-
#
|
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
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
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
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
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
|
-
|
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
|
-
#
|
40
|
-
# `
|
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
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
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
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
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
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
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
|
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 {
|
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
|
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
|
data/lib/checkpoint/db.rb
CHANGED
@@ -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/
|
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
|
6
|
-
class
|
7
|
-
# Instantiate a
|
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:
|
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,
|