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
@@ -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