checkpoint 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,
|