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
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint
|
4
|
+
module DB
|
5
|
+
# A helper for building placeholder variable names from items in a list and
|
6
|
+
# providing a corresponding hash of values. A prefix with some mnemonic
|
7
|
+
# corresponding to the column is recommended. For example, if the column is
|
8
|
+
# `agent_token`, using the prefix `at` will yield `$at_0`, `$at_1`, etc. for
|
9
|
+
# an IN clause.
|
10
|
+
class Params
|
11
|
+
attr_reader :items, :prefix
|
12
|
+
|
13
|
+
def initialize(items, prefix)
|
14
|
+
@items = [items].flatten
|
15
|
+
@prefix = prefix
|
16
|
+
end
|
17
|
+
|
18
|
+
def placeholders
|
19
|
+
0.upto(items.size - 1).map do |i|
|
20
|
+
:"$#{prefix}_#{i}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def values
|
25
|
+
items.map.with_index do |item, i|
|
26
|
+
value = if item.respond_to?(:sql_value)
|
27
|
+
item.sql_value
|
28
|
+
else
|
29
|
+
item.to_s
|
30
|
+
end
|
31
|
+
[:"#{prefix}_#{i}", value]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint::DB
|
4
|
+
module Query
|
5
|
+
# A query object based on agents and credentials.
|
6
|
+
#
|
7
|
+
# This query finds grants for any supplied agents, for any supplied
|
8
|
+
# credentials. Its primary purpose is to find which resources for which an
|
9
|
+
# agent has been granted a given credential.
|
10
|
+
#
|
11
|
+
# It can take single items or arrays and converts them all to their tokens
|
12
|
+
# for query purposes.
|
13
|
+
class AC < CartesianSelect
|
14
|
+
attr_reader :agents, :credentials
|
15
|
+
|
16
|
+
def initialize(agents, credentials, scope: Grant)
|
17
|
+
super(scope: scope)
|
18
|
+
@agents = tokenize(agents)
|
19
|
+
@credentials = tokenize(credentials)
|
20
|
+
end
|
21
|
+
|
22
|
+
def conditions
|
23
|
+
super.merge(
|
24
|
+
agent_token: agent_params.placeholders,
|
25
|
+
credential_token: credential_params.placeholders
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def parameters
|
30
|
+
super.merge(Hash[
|
31
|
+
agent_params.values +
|
32
|
+
credential_params.values
|
33
|
+
])
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def agent_params
|
39
|
+
Params.new(agents, 'at')
|
40
|
+
end
|
41
|
+
|
42
|
+
def credential_params
|
43
|
+
Params.new(credentials, 'ct')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint::DB
|
4
|
+
module Query
|
5
|
+
# A query object based on agents, credentials, and resources.
|
6
|
+
#
|
7
|
+
# This query mirrors the essence of the Checkpoint semantics; that is, it
|
8
|
+
# finds grants for any supplied agents, for any supplied credentials, for
|
9
|
+
# any supplied resources.
|
10
|
+
#
|
11
|
+
# It can take single items or arrays and converts them all to their tokens
|
12
|
+
# for query purposes.
|
13
|
+
class ACR < CartesianSelect
|
14
|
+
attr_reader :agents, :credentials, :resources
|
15
|
+
|
16
|
+
def initialize(agents, credentials, resources, scope: Grant)
|
17
|
+
super(scope: scope)
|
18
|
+
@agents = tokenize(agents)
|
19
|
+
@credentials = tokenize(credentials)
|
20
|
+
@resources = tokenize(resources)
|
21
|
+
end
|
22
|
+
|
23
|
+
def conditions
|
24
|
+
super.merge(
|
25
|
+
agent_token: agent_params.placeholders,
|
26
|
+
credential_token: credential_params.placeholders,
|
27
|
+
resource_token: resource_params.placeholders
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def parameters
|
32
|
+
super.merge(Hash[
|
33
|
+
agent_params.values +
|
34
|
+
credential_params.values +
|
35
|
+
resource_params.values
|
36
|
+
])
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def agent_params
|
42
|
+
Params.new(agents, 'at')
|
43
|
+
end
|
44
|
+
|
45
|
+
def credential_params
|
46
|
+
Params.new(credentials, 'ct')
|
47
|
+
end
|
48
|
+
|
49
|
+
def resource_params
|
50
|
+
Params.new(resources, 'rt')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint::DB
|
4
|
+
module Query
|
5
|
+
# A query object based on agents and resources.
|
6
|
+
#
|
7
|
+
# This query finds grants for any supplied agents, for any supplied
|
8
|
+
# resources. Its primary purpose is to find which credentials have been
|
9
|
+
# granted to an agent on a given resource.
|
10
|
+
#
|
11
|
+
# It can take single items or arrays and converts them all to their tokens
|
12
|
+
# for query purposes.
|
13
|
+
class AR < CartesianSelect
|
14
|
+
attr_reader :agents, :resources
|
15
|
+
|
16
|
+
def initialize(agents, resources, scope: Grant)
|
17
|
+
super(scope: scope)
|
18
|
+
@agents = tokenize(agents)
|
19
|
+
@resources = tokenize(resources)
|
20
|
+
end
|
21
|
+
|
22
|
+
def conditions
|
23
|
+
super.merge(
|
24
|
+
agent_token: agent_params.placeholders,
|
25
|
+
resource_token: resource_params.placeholders
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def parameters
|
30
|
+
super.merge(Hash[
|
31
|
+
agent_params.values +
|
32
|
+
resource_params.values
|
33
|
+
])
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def agent_params
|
39
|
+
Params.new(agents, 'at')
|
40
|
+
end
|
41
|
+
|
42
|
+
def resource_params
|
43
|
+
Params.new(resources, 'rt')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint::DB
|
4
|
+
module Query
|
5
|
+
# A query object based on credentials and resources.
|
6
|
+
#
|
7
|
+
# This query finds grants for any supplied credentials, for any supplied
|
8
|
+
# resources. Its primary purpose is to find which agents have been granted
|
9
|
+
# a given credential on a resource.
|
10
|
+
#
|
11
|
+
# It can take single items or arrays and converts them all to their tokens
|
12
|
+
# for query purposes.
|
13
|
+
class CR < CartesianSelect
|
14
|
+
attr_reader :credentials, :resources
|
15
|
+
|
16
|
+
def initialize(credentials, resources, scope: Grant)
|
17
|
+
super(scope: scope)
|
18
|
+
@credentials = tokenize(credentials)
|
19
|
+
@resources = tokenize(resources)
|
20
|
+
end
|
21
|
+
|
22
|
+
def conditions
|
23
|
+
super.merge(
|
24
|
+
credential_token: credential_params.placeholders,
|
25
|
+
resource_token: resource_params.placeholders
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def parameters
|
30
|
+
super.merge(Hash[
|
31
|
+
credential_params.values +
|
32
|
+
resource_params.values
|
33
|
+
])
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def credential_params
|
39
|
+
Params.new(credentials, 'ct')
|
40
|
+
end
|
41
|
+
|
42
|
+
def resource_params
|
43
|
+
Params.new(resources, 'rt')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Note: we do not require db/grant because Sequel requires the connection
|
4
|
+
# to be set up before defining the model classes. The arrangment here
|
5
|
+
# assumes that DB.initialize! will have been called if the default model
|
6
|
+
# is to be used. In tests, that is done by spec/sequel_helper.rb. In an
|
7
|
+
# application, there should be an initializer that reads whatever appropriate
|
8
|
+
# configuration and does the initialization.
|
9
|
+
|
10
|
+
require 'checkpoint/db'
|
11
|
+
|
12
|
+
module Checkpoint
|
13
|
+
# The repository of grants -- a simple wrapper for the Sequel Datastore / grants table.
|
14
|
+
class Grants
|
15
|
+
def initialize(grants: Checkpoint::DB::Grant)
|
16
|
+
@grants = grants
|
17
|
+
end
|
18
|
+
|
19
|
+
def for(agents, credentials, resources)
|
20
|
+
where(agents, credentials, resources).all
|
21
|
+
end
|
22
|
+
|
23
|
+
def any?(agents, credentials, resources)
|
24
|
+
where(agents, credentials, resources).first != nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Find grants of the given credentials on the given resources.
|
28
|
+
#
|
29
|
+
# This is useful for finding who should have particular access. Note that
|
30
|
+
# this low-level interface returns the full grants, rather than a unique
|
31
|
+
# set of agents.
|
32
|
+
#
|
33
|
+
# @return [Array<Grant>] the set of grants of any of the credentials on
|
34
|
+
# any of the resources
|
35
|
+
def who(credentials, resources)
|
36
|
+
DB::Query::CR.new(credentials, resources, **scope).all
|
37
|
+
end
|
38
|
+
|
39
|
+
# Find grants to the given agents on the given resources.
|
40
|
+
#
|
41
|
+
# This is useful for finding what actions may be taken on particular items.
|
42
|
+
# Note that this low-level interface returns the full grants, rather than a
|
43
|
+
# unique set of credentials.
|
44
|
+
#
|
45
|
+
# @return [Array<Grant>] the set of grants to any of the agents on any of
|
46
|
+
# the resources
|
47
|
+
def what(agents, resources)
|
48
|
+
DB::Query::AR.new(agents, resources, **scope).all
|
49
|
+
end
|
50
|
+
|
51
|
+
# Find grants to the given agents of the given credentials.
|
52
|
+
#
|
53
|
+
# This is useful for finding which resources may acted upon. Note that this
|
54
|
+
# low-level interface returns the full grants, rather than a unique set of
|
55
|
+
# resources.
|
56
|
+
#
|
57
|
+
# @return [Array<Grant>] the set of grants of any of the credentials to
|
58
|
+
# any of the agents
|
59
|
+
def which(agents, credentials)
|
60
|
+
DB::Query::AC.new(agents, credentials, **scope).all
|
61
|
+
end
|
62
|
+
|
63
|
+
# Grant a credential.
|
64
|
+
#
|
65
|
+
# This method takes a single agent, credential, and resource to create a
|
66
|
+
# grant. They are not expanded, though they may be general (e.g., an
|
67
|
+
# agent for users of an instituion or a wildcard for resources of some type).
|
68
|
+
#
|
69
|
+
# @param agent [Agent] the agent to whom the credential should be granted
|
70
|
+
# @param credential [Credential] the credential to grant
|
71
|
+
# @param resource [Resource] the resource to which the credential should apply
|
72
|
+
# @return [Grant] the saved Grant; nil if the save fails
|
73
|
+
def grant!(agent, credential, resource)
|
74
|
+
grants.from(agent, credential, resource).save
|
75
|
+
end
|
76
|
+
|
77
|
+
# Revoke a credential.
|
78
|
+
#
|
79
|
+
# Take care to note that this follows the same matching semantics as
|
80
|
+
# {.for}. There is no expansion done here, but anything that matches what
|
81
|
+
# is supplied will be deleted. Of particular note is the default wildcard
|
82
|
+
# behavior of {Checkpoint::Resource::Resolver}: if a specific resource has
|
83
|
+
# been expanded by the resolver, and the array of the resource, a type
|
84
|
+
# wildcard, and the any-resource wildcard (as used for inherited matching)
|
85
|
+
# is supplied, the results may be surprising where there are grants at
|
86
|
+
# specific and general levels.
|
87
|
+
#
|
88
|
+
# In general, the parameters should not have been expanded. If the intent
|
89
|
+
# is to revoke a general grant, the general details should be supplied,
|
90
|
+
# and likewise for the specific case.
|
91
|
+
#
|
92
|
+
# Applications should interact with the {Checkpoint::Authority}, which
|
93
|
+
# exposes a more application-oriented interface. This repository should be
|
94
|
+
# considered internal to Checkpoint.
|
95
|
+
#
|
96
|
+
# @param agents [Agent|Array] the agent or agents to match for deletion
|
97
|
+
# @param credentials [Credential|Array] the credential or credentials to match for deletion
|
98
|
+
# @param resources [Resource|Array] the resource or resources to match for deletion
|
99
|
+
# @return [Integer] the number of Grants deleted
|
100
|
+
def revoke!(agents, credentials, resources)
|
101
|
+
where(agents, credentials, resources).delete
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def scope
|
107
|
+
{ scope: grants }
|
108
|
+
end
|
109
|
+
|
110
|
+
def where(agents, credentials, resources)
|
111
|
+
DB::Query::ACR.new(agents, credentials, resources, **scope)
|
112
|
+
end
|
113
|
+
|
114
|
+
attr_reader :grants
|
115
|
+
end
|
116
|
+
end
|
@@ -16,7 +16,7 @@ module Checkpoint
|
|
16
16
|
# to examine. Tests can validate system behavior at development time
|
17
17
|
# because it is static.
|
18
18
|
#
|
19
|
-
# 2. Implementing a {Checkpoint::Credential::
|
19
|
+
# 2. Implementing a {Checkpoint::Credential::Resolver} that maps backward
|
20
20
|
# from actions to named permissions and roles that would allow them.
|
21
21
|
# The policy rules would only authorize actions, leaving the mapping
|
22
22
|
# outside to accommodate configuration or runtime modification. This has
|
data/lib/checkpoint/resource.rb
CHANGED
@@ -35,7 +35,7 @@ module Checkpoint
|
|
35
35
|
class Resource
|
36
36
|
attr_reader :entity
|
37
37
|
|
38
|
-
# Special string to be used when
|
38
|
+
# Special string to be used when granting or searching for grants on all
|
39
39
|
# types or all resources
|
40
40
|
ALL = '(all)'
|
41
41
|
|
@@ -46,19 +46,6 @@ module Checkpoint
|
|
46
46
|
@entity = entity
|
47
47
|
end
|
48
48
|
|
49
|
-
# Default conversion from an entity to a Resource. Prefer this to creating
|
50
|
-
# new instances by hand.
|
51
|
-
#
|
52
|
-
# If the entity implements #to_resource, we will delegate to it. Otherwise,
|
53
|
-
# we will return a Resource for this entity.
|
54
|
-
def self.from(entity)
|
55
|
-
if entity.respond_to?(:to_resource)
|
56
|
-
entity.to_resource
|
57
|
-
else
|
58
|
-
new(entity)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
49
|
# Covenience factory method to get a Resource that will match all entities
|
63
50
|
# of any type.
|
64
51
|
#
|
@@ -67,11 +54,22 @@ module Checkpoint
|
|
67
54
|
AllOfAnyType.new
|
68
55
|
end
|
69
56
|
|
57
|
+
# Convert this object to a Resource.
|
58
|
+
#
|
59
|
+
# For Checkpoint-supplied Resources, this is an identity operation,
|
60
|
+
# but it allows consistent handling of the built-in types and
|
61
|
+
# application-supplied types that will either implement this interface or
|
62
|
+
# convert themselves to a built-in type. This removes the requirement to
|
63
|
+
# extend Checkpoint types or bind to a specific conversion method.
|
64
|
+
def to_resource
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
70
68
|
# Get the resource type.
|
71
69
|
#
|
72
70
|
# Note that this is not necessarily a class/model type name. It can be
|
73
71
|
# whatever type name is most useful for building tokens and inspecting
|
74
|
-
#
|
72
|
+
# grants for this types. For example, there may be objects that have
|
75
73
|
# subtypes that are not modeled as objects, decorators, or collection
|
76
74
|
# objects (like a specialized type for the root of a tree) that should
|
77
75
|
# be treated as the element type.
|
@@ -127,12 +125,11 @@ module Checkpoint
|
|
127
125
|
other.is_a?(Resource) && entity.eql?(other.entity)
|
128
126
|
end
|
129
127
|
|
130
|
-
# Check whether two Resources refer to the same entity.
|
128
|
+
# Check whether two Resources refer to the same entity by type and id.
|
131
129
|
# @param other [Resource] Another Resource to compare with
|
132
|
-
# @return [Boolean] true when the other Resource's
|
133
|
-
# determined by comparing them with `#==`.
|
130
|
+
# @return [Boolean] true when the other Resource's type and id are equal.
|
134
131
|
def ==(other)
|
135
|
-
other.is_a?(Resource) &&
|
132
|
+
other.is_a?(Resource) && type == other.type && id == other.id
|
136
133
|
end
|
137
134
|
end
|
138
135
|
end
|
@@ -12,14 +12,6 @@ module Checkpoint
|
|
12
12
|
@entity = AnyEntity.new
|
13
13
|
end
|
14
14
|
|
15
|
-
# Create a wildcard Resource "from" an entity. The entity disregarded and
|
16
|
-
# {AnyEntity} is substituted.
|
17
|
-
#
|
18
|
-
# @return [AllOfAnyType] a wildcard Resource instance
|
19
|
-
def self.from(_entity)
|
20
|
-
new
|
21
|
-
end
|
22
|
-
|
23
15
|
# The special ALL type
|
24
16
|
def type
|
25
17
|
Resource::ALL
|
@@ -10,16 +10,6 @@ module Checkpoint
|
|
10
10
|
@type = type
|
11
11
|
end
|
12
12
|
|
13
|
-
# Create a type-specific wildcard Resource from a given entity
|
14
|
-
#
|
15
|
-
# When the entity implements to #to_resource, we convert it first and take
|
16
|
-
# the type from the result. Otherwise, when it implements #resource_type,
|
17
|
-
# we use that result. Otherwise, we take the class name of the entity.
|
18
|
-
# Regardless of the source, the type is forced to a string.
|
19
|
-
def self.from(entity)
|
20
|
-
new(type_of(entity))
|
21
|
-
end
|
22
|
-
|
23
13
|
# This is always the special ALL resource ID
|
24
14
|
def id
|
25
15
|
Resource::ALL
|
@@ -32,18 +22,6 @@ module Checkpoint
|
|
32
22
|
other.is_a?(Resource) && type == other.type
|
33
23
|
end
|
34
24
|
|
35
|
-
# Private type name extraction
|
36
|
-
def self.type_of(entity)
|
37
|
-
if entity.respond_to?(:to_resource)
|
38
|
-
entity.to_resource.type
|
39
|
-
elsif entity.respond_to?(:resource_type)
|
40
|
-
entity.resource_type
|
41
|
-
else
|
42
|
-
entity.class
|
43
|
-
end.to_s
|
44
|
-
end
|
45
|
-
|
46
|
-
private_class_method :type_of
|
47
25
|
alias == eql?
|
48
26
|
end
|
49
27
|
end
|