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.
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
@@ -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::Mapper} that maps backward
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
@@ -35,7 +35,7 @@ module Checkpoint
35
35
  class Resource
36
36
  attr_reader :entity
37
37
 
38
- # Special string to be used when permitting or searching for permits on all
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
- # permits for this types. For example, there may be objects that have
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 entity is the same as
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) && entity == other.entity
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