checkpoint 0.2.2 → 1.0.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 +7 -0
- data/.envrc +1 -0
- data/.gitignore +18 -9
- data/.rspec +2 -0
- data/.rubocop.yml +30 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/Gemfile +5 -1
- data/LICENSE.md +27 -0
- data/README.md +23 -0
- data/Rakefile +14 -0
- data/bin/console +18 -0
- data/bin/rake +21 -0
- data/bin/rspec +21 -0
- data/bin/sequel +21 -0
- data/bin/setup +8 -0
- data/bin/yard +21 -0
- data/bin/yardoc +21 -0
- data/checkpoint.gemspec +37 -19
- data/db/migrations/1_create_permits.rb +19 -0
- data/docs/Makefile +24 -0
- data/docs/_static/.gitkeep +0 -0
- data/docs/_templates/.gitkeep +0 -0
- data/docs/authentication.rst +18 -0
- data/docs/conf.py +46 -0
- data/docs/index.rst +28 -0
- data/docs/policies.rst +211 -0
- data/docs/requirements.txt +4 -0
- data/lib/checkpoint.rb +16 -2
- data/lib/checkpoint/agent.rb +93 -0
- data/lib/checkpoint/agent/resolver.rb +33 -0
- data/lib/checkpoint/agent/token.rb +52 -0
- data/lib/checkpoint/authority.rb +67 -0
- data/lib/checkpoint/credential.rb +82 -0
- data/lib/checkpoint/credential/permission.rb +27 -0
- data/lib/checkpoint/credential/resolver.rb +87 -0
- data/lib/checkpoint/credential/role.rb +26 -0
- data/lib/checkpoint/credential/token.rb +51 -0
- data/lib/checkpoint/db.rb +161 -0
- data/lib/checkpoint/db/permit.rb +24 -0
- data/lib/checkpoint/permission_mapper.rb +29 -0
- data/lib/checkpoint/permits.rb +133 -0
- data/lib/checkpoint/query.rb +42 -0
- data/lib/checkpoint/query/action_permitted.rb +40 -0
- data/lib/checkpoint/query/role_granted.rb +55 -0
- data/lib/checkpoint/railtie.rb +92 -71
- data/lib/checkpoint/resource.rb +138 -0
- data/lib/checkpoint/resource/all_of_any_type.rb +34 -0
- data/lib/checkpoint/resource/all_of_type.rb +50 -0
- data/lib/checkpoint/resource/any_entity.rb +25 -0
- data/lib/checkpoint/resource/any_entity_of_type.rb +29 -0
- data/lib/checkpoint/resource/resolver.rb +21 -0
- data/lib/checkpoint/resource/token.rb +65 -0
- data/lib/checkpoint/version.rb +3 -1
- data/lib/tasks/migrate.rake +75 -0
- metadata +260 -19
- data/Readme.markdown +0 -103
data/docs/index.rst
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
.. title:: Checkpoint, policy-based authorization in Ruby
|
2
|
+
|
3
|
+
Checkpoint Documentation
|
4
|
+
========================
|
5
|
+
|
6
|
+
Checkpoint is both a Ruby library and an abstract model for authorizing user
|
7
|
+
activity within an application.
|
8
|
+
|
9
|
+
It takes its name from the concept of a physical security checkpoint, where
|
10
|
+
someone (or some device) verifies identity and credentials before granting
|
11
|
+
access to some space or resource. This documentation covers a range of topics
|
12
|
+
related to authentication and authorization, primarily focused on how to
|
13
|
+
implement secure, understandable, and maintainable business rules in web
|
14
|
+
applications built in Ruby. It comes out of the University of Michigan Library,
|
15
|
+
where enterprise, legacy, and new systems must all interoperate.
|
16
|
+
|
17
|
+
Checkpoint emphasizes the use of policies and object-oriented design, giving
|
18
|
+
examples from very simple rules through complex group- and role-based scenarios.
|
19
|
+
|
20
|
+
Table of Contents
|
21
|
+
-----------------
|
22
|
+
|
23
|
+
.. toctree::
|
24
|
+
:maxdepth: 2
|
25
|
+
|
26
|
+
authentication
|
27
|
+
policies
|
28
|
+
|
data/docs/policies.rst
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
Policies
|
2
|
+
========
|
3
|
+
|
4
|
+
A single, consistent model for authorizing user actions is helpful in
|
5
|
+
developing secure, correct applications. One basic approach to this is to use
|
6
|
+
what are called **Policy Objects** (or simply **policies**). This is somewhat
|
7
|
+
different than declarative or hybrid systems like CanCan, for example.
|
8
|
+
|
9
|
+
With CanCan, there is a convenient method for declaring that a user "can do"
|
10
|
+
something, building up a map of the users' permitted actions on each request.
|
11
|
+
This breaks down somewhat when dynamic roles and object state must be
|
12
|
+
considered. The typical solution is to provide a block that serves as a boolean
|
13
|
+
predicate for a given operation. This leads to a hybrid of statements and
|
14
|
+
predicates, with no clear pattern for what should be placed where.
|
15
|
+
|
16
|
+
The policy approach is slightly different in that it assumes there will be an
|
17
|
+
object that can answer "yes" or "no" to the question of whether a given user
|
18
|
+
can take a given action. This **rule** may need detailed contextual information
|
19
|
+
like two objects that the user would like to associate, but the "answer" is not
|
20
|
+
precomputed. This consistency is useful because object-oriented principles can
|
21
|
+
be applied readily, giving handy tools for organizing and composing the domain
|
22
|
+
rules.
|
23
|
+
|
24
|
+
There is some variation in how policies are constructed, particularly in
|
25
|
+
whether they are resource- or operation-oriented. Resource-oriented policies
|
26
|
+
tend to have one object covering multiple REST/CRUD operations with associative
|
27
|
+
operations being covered on a separate policy for logical resources (e.g., a
|
28
|
+
"membership"). Operation-oriented policies tend to cover one domain action per
|
29
|
+
object, though this is a flexible design decision where a policy may cover a
|
30
|
+
number of related actions.
|
31
|
+
|
32
|
+
For the sake of the following discussion, a rule should be considered as
|
33
|
+
applying for a specific action, without regard for whether it is the only rule
|
34
|
+
of a policy or one of multiple rules on a broader policy.
|
35
|
+
|
36
|
+
|
37
|
+
Rules vs. Facts
|
38
|
+
---------------
|
39
|
+
|
40
|
+
One important distinction is that of **Rules** versus **Facts**. A given rule
|
41
|
+
will generally be static but may consider dynamic facts. For example, a rule
|
42
|
+
may state that a user may only delete documents they own. The rule considers
|
43
|
+
dynamic facts of who is acting and the owner of the object acted upon; it has a
|
44
|
+
context of two parameters.
|
45
|
+
|
46
|
+
Types of Rules
|
47
|
+
--------------
|
48
|
+
|
49
|
+
Rules sit on a continuum of how dynamic their context is; that is, how much
|
50
|
+
external information is needed to make the decision, and where that information
|
51
|
+
comes from. The extent to which a rule uses dynamic information can inform how
|
52
|
+
it is constructed.
|
53
|
+
|
54
|
+
In general, rules that consider few and simple facts should be implemented as
|
55
|
+
directly as possible, in the source code, and without calling out to additional
|
56
|
+
collaborators. This facilitates understanding and testing by limiting the scope
|
57
|
+
of what could result in different answers.
|
58
|
+
|
59
|
+
Rules that consider more facts and those that are complicated to derive deserve
|
60
|
+
some indirection as to avoid coupling to complexity that does not belong to the
|
61
|
+
rule itself, as well as duplication of conceptual elements (such as how role
|
62
|
+
hierarchy should imply additional permissions).
|
63
|
+
|
64
|
+
Some patterns can be applied to the rule code and fact data to avoid repeating
|
65
|
+
design activity and incidental variation that is unhelpful across multiple
|
66
|
+
applications in an organization. These examples are not necessarily exhaustive,
|
67
|
+
but aim to provide a representative range with extremes identified.
|
68
|
+
|
69
|
+
Totally Static
|
70
|
+
~~~~~~~~~~~~~~
|
71
|
+
|
72
|
+
The rule is encoded in the application and will yield the same answer until the
|
73
|
+
source code is changed. This may be used a placeholder for a permission check
|
74
|
+
for a feature not yet enabled or as a deprecation tool to disable an action
|
75
|
+
while source-level invocations are removed. This type of rule is uncommon, only
|
76
|
+
really useful in transition to richer implementation or removal.
|
77
|
+
|
78
|
+
Configuration-Only
|
79
|
+
~~~~~~~~~~~~~~~~~~
|
80
|
+
|
81
|
+
The rule considers only a system configuration value and no other contextual
|
82
|
+
information. This is functionally equivalent but semantically preferable to a
|
83
|
+
"feature flag" because the conceptual focus remains on whether the user is
|
84
|
+
permitted to take some action. The policy becomes a simple indirection point,
|
85
|
+
behind which the implementation of making the decision (flag, role, etc.) can
|
86
|
+
vary without changing the calling code.
|
87
|
+
|
88
|
+
An example of a user-oriented (but not user-specific) setting might be "delete
|
89
|
+
items", set in a system configuration file. Whether the action is permitted
|
90
|
+
should be conditioned by a rule that consults the setting. This type of rule
|
91
|
+
often evolves to account for concerns like whether the user has administrator
|
92
|
+
access or owns an object. Especially if the permission would be checked in
|
93
|
+
multiple places, a rule is preferable to maintain consistency of authorization
|
94
|
+
and discourage "authorization in disguise", such as scattered checks against a
|
95
|
+
flag or whether the user is an administrator.
|
96
|
+
|
97
|
+
Note that feature flags are appropriate to consult directly when system
|
98
|
+
behavior should be conditional, but is unlikely to vary based on the acting
|
99
|
+
user. An example of this might be whether the implementors have decided to
|
100
|
+
enable "social widgets" or PDF export across an entire web application. This is
|
101
|
+
not a matter of authorization, so it should not be implemented as a rule.
|
102
|
+
|
103
|
+
User and Resource
|
104
|
+
~~~~~~~~~~~~~~~~~
|
105
|
+
|
106
|
+
The rule considers the acting user in combination with a target resource or
|
107
|
+
multiple resources. This is a very common rule type for which the policy
|
108
|
+
approach shines in comparison to the declarative or hybrid approaches. Rules of
|
109
|
+
this type will often check that the user owns the resource or a container, a
|
110
|
+
user attribute like "is administrator", object state such as whether a document
|
111
|
+
is in draft mode, or conditions such as publication or retraction dates versus
|
112
|
+
the current time.
|
113
|
+
|
114
|
+
This type of rule is usually quite concise in its expression in source code,
|
115
|
+
which helps clarify behavior so that it can be implemented, verified,
|
116
|
+
communicated, and maintained with confidence.
|
117
|
+
|
118
|
+
User, Resource, and Context
|
119
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
120
|
+
|
121
|
+
The rule considers the user, the target resource, and contextual information
|
122
|
+
such as region as determined by IP address, usage quotas, or configuration. A
|
123
|
+
small extension to *User and Resource* rule type, the additional context is not
|
124
|
+
usually supplied by the calling code (as are the user and resource), but
|
125
|
+
retrieved from the environment or usage session information.
|
126
|
+
|
127
|
+
System Role and Resource
|
128
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
129
|
+
|
130
|
+
Rules of this type are similar to *User and Resource* rules, but they also
|
131
|
+
consider the user's system-wide role, typically an attribute of the user from a
|
132
|
+
fixed set of application roles. An administrator flag is commonly converted to
|
133
|
+
a named role when using this model.
|
134
|
+
|
135
|
+
For this discussion, a role implies a fixed set of permitted actions. For example, an
|
136
|
+
"editor" may be able to update items, but not delete them. This would be encoded
|
137
|
+
directly in the rule, only changing when the application changes.
|
138
|
+
|
139
|
+
Agent and Resource
|
140
|
+
~~~~~~~~~~~~~~~~~~
|
141
|
+
|
142
|
+
The rule considers the acting user and the target resource, in light of
|
143
|
+
permitted agents. An **agent** is an abstraction of a permitted user or group.
|
144
|
+
There is some semantic overlap in common usage of the terms group and role. For
|
145
|
+
this discussion, a group serves as a simple set of users, not directly implying
|
146
|
+
any permitted actions like a role would. A group could serve as a container for
|
147
|
+
resources or be permitted to take actions on a resource by a property such as
|
148
|
+
"shared with". The agent abstraction allows actions to be permitted to single
|
149
|
+
users or groups without spreading conditional handling for both cases
|
150
|
+
throughout an application.
|
151
|
+
|
152
|
+
A rule of this type will commonly check the equivalent of information like
|
153
|
+
whether the user owns the resource, if the resource is shared directly with the
|
154
|
+
user, or if any of the groups to which the user belongs matches any of the
|
155
|
+
groups that the resource is shared with.
|
156
|
+
|
157
|
+
The agent abstraction reduces this type of check to whether the agent owns the
|
158
|
+
resource or if the resource is shared with the agent. The rules can remain
|
159
|
+
simple with the consistent semantics of "user or group" externalized.
|
160
|
+
|
161
|
+
Agent, Resource, Context, Role, and Permissions
|
162
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
163
|
+
|
164
|
+
The rule considers all of the above information in addition to dynamic
|
165
|
+
permissions granted to roles. The roles may be fixed by the application or
|
166
|
+
configurable. The permissions implied by a role are not fixed and must be
|
167
|
+
resolved at runtime. The configuration of the permissions may occur as a
|
168
|
+
customization of a deployed source file, a system configuration file read at
|
169
|
+
startup, or persisted elsewhere.
|
170
|
+
|
171
|
+
Inspecting rules of this type is much more abstract and requires significantly
|
172
|
+
more knowledge of the deployment infrastructure and configuration model,
|
173
|
+
especially if the permissions are granted in a database. Tests can only verify
|
174
|
+
that the right authorization questions are asked and answered as expected for
|
175
|
+
example configurations; they are no longer effective for verifying that an
|
176
|
+
application will behave as desired in production. The implementors hold the
|
177
|
+
responsibility for ensuring that the configuration is correct.
|
178
|
+
|
179
|
+
Significant tooling is typically built to allow runtime inspection or
|
180
|
+
modification of permissions within applications with this level of flexibility.
|
181
|
+
|
182
|
+
"One Rule"
|
183
|
+
~~~~~~~~~~
|
184
|
+
|
185
|
+
In scenarios where groups, roles, permissions, and actions must be allowed to be
|
186
|
+
defined at runtime, the rules tend to become very generic while the "fact" data
|
187
|
+
becomes very detailed. Almost no literal values will be used in a rule, relying
|
188
|
+
on assembly of a set of attributes and requirements from persisted data. Taken
|
189
|
+
far enough, this approach sometimes reduces the set of business rules to "one
|
190
|
+
rule" serving as a first-order logic solver over a fully dynamic set of facts.
|
191
|
+
All parts of the application must make authorization requests in a generic
|
192
|
+
format to a single point of control.
|
193
|
+
|
194
|
+
Systems needing this level of flexibility are rare. For example, it is
|
195
|
+
sometimes seen in the ERP and CMS spaces where installers build workflows,
|
196
|
+
modules, and whole business applications in that environment at runtime -- the
|
197
|
+
developers of the base application implement a development environment as much
|
198
|
+
as a specific application. Inspection or modification of rules and facts in a
|
199
|
+
system of this complexity requires extensive operational tooling and expertise.
|
200
|
+
|
201
|
+
Enterprise Authorization
|
202
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
203
|
+
|
204
|
+
Some enterprises externalize application rules to policy systems. The
|
205
|
+
applications formulate requests in a standardized format, being explicit about
|
206
|
+
the subject, resources, and action in terms of enterprise identifiers. These
|
207
|
+
requests are then validated against policies managed at the enterprise level.
|
208
|
+
This approach provides consistency across applications and services at an
|
209
|
+
enormous complexity and operational cost, hence it will not be under further
|
210
|
+
discussion here.
|
211
|
+
|
data/lib/checkpoint.rb
CHANGED
@@ -1,7 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "checkpoint/version"
|
2
|
-
require "checkpoint/railtie"
|
3
4
|
|
5
|
+
require 'sequel'
|
6
|
+
require 'mysql2'
|
7
|
+
require 'ettin'
|
4
8
|
|
9
|
+
# All of the Checkpoint components are contained within this top-level module.
|
5
10
|
module Checkpoint
|
6
|
-
#
|
11
|
+
# An error raised if there is no callable identifier on an entity when
|
12
|
+
# attempting to convert it to a resource.
|
13
|
+
class NoIdentifierError < StandardError; end
|
7
14
|
end
|
15
|
+
|
16
|
+
require 'checkpoint/agent'
|
17
|
+
require 'checkpoint/credential'
|
18
|
+
require 'checkpoint/resource'
|
19
|
+
require 'checkpoint/authority'
|
20
|
+
require 'checkpoint/query'
|
21
|
+
require 'checkpoint/railtie' if defined?(Rails)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'checkpoint/agent/resolver'
|
4
|
+
require 'checkpoint/agent/token'
|
5
|
+
|
6
|
+
module Checkpoint
|
7
|
+
# An Agent is an any person or entity that might be granted various
|
8
|
+
# permission, such as a user, group, or institution.
|
9
|
+
#
|
10
|
+
# The application objects that an agent represents may be of any type; this
|
11
|
+
# is more of an interface or role than a base class. The important concept is
|
12
|
+
# that permits are granted to agents, and that agents may be representative
|
13
|
+
# of multiple concrete actors, such as any person affiliated with a given
|
14
|
+
# institution or any member of a given group.
|
15
|
+
class Agent
|
16
|
+
attr_accessor :actor
|
17
|
+
|
18
|
+
# Create an Agent. This should not be called externally; use {::from} instead.
|
19
|
+
def initialize(actor)
|
20
|
+
@actor = actor
|
21
|
+
end
|
22
|
+
|
23
|
+
# Default conversion from an actor to an {Agent}.
|
24
|
+
#
|
25
|
+
# If the actor implements #to_agent, we will delegate to it. Otherwise,
|
26
|
+
# we check if the actor implements #agent_type or #agent_id; if so, we
|
27
|
+
# use them as the type and id, respectively. If not, we use the actor's
|
28
|
+
# class name as the type and call #id for the id. If the actor does not
|
29
|
+
# implement any of the ways to supply an #id, an error will be raised.
|
30
|
+
#
|
31
|
+
# @return [Agent] the actor converted to an agent
|
32
|
+
def self.from(actor)
|
33
|
+
if actor.respond_to?(:to_agent)
|
34
|
+
actor.to_agent
|
35
|
+
else
|
36
|
+
new(actor)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the captive actor's type.
|
41
|
+
#
|
42
|
+
# If the entity implements `#to_agent`, we will call that and use the
|
43
|
+
# returned agent's type. If not, but it implements `#agent_type`, we
|
44
|
+
# will use that. Otherwise, we use the actors's class name.
|
45
|
+
#
|
46
|
+
# @return [String] the name of the actor's type after calling `#to_s` on it.
|
47
|
+
def type
|
48
|
+
if actor.respond_to?(:agent_type)
|
49
|
+
actor.agent_type
|
50
|
+
else
|
51
|
+
actor.class
|
52
|
+
end.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get the captive actor's id.
|
56
|
+
#
|
57
|
+
# If the entity implements `#to_agent`, we will call that and use the
|
58
|
+
# returned agent's id. If not, but it implements `#agent_id`, we
|
59
|
+
# will use that. Otherwise, we call `#id`. If the the actor does not
|
60
|
+
# implement any of these methods, we raise a {NoIdentifierError}.
|
61
|
+
#
|
62
|
+
# @return [String] the name of the actor's type after calling `#to_s` on it.
|
63
|
+
def id
|
64
|
+
if actor.respond_to?(:agent_id)
|
65
|
+
actor.agent_id
|
66
|
+
elsif actor.respond_to?(:id)
|
67
|
+
actor.id
|
68
|
+
else
|
69
|
+
raise NoIdentifierError, "No usable identifier on actor of type: #{actor.class}"
|
70
|
+
end.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def token
|
74
|
+
@token ||= Token.new(type, id)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Check whether two Agents refer to the same concrete actor.
|
78
|
+
# @param other [Agent] Another Agent to compare with
|
79
|
+
# @return [Boolean] true when the other Agent's actor is the same as
|
80
|
+
# determined by comparing them with `#eql?`.
|
81
|
+
def eql?(other)
|
82
|
+
other.is_a?(Agent) && actor.eql?(other.actor)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Check whether two Agents refer to the same concrete actor.
|
86
|
+
# @param other [Agent] Another Agent to compare with
|
87
|
+
# @return [Boolean] true when the other Agent's actor is the same as
|
88
|
+
# determined by comparing them with `==`.
|
89
|
+
def ==(other)
|
90
|
+
other.is_a?(Agent) && actor == other.actor
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint
|
4
|
+
class Agent
|
5
|
+
# An Agent Resolver takes a concrete user (or other account/actor) object and
|
6
|
+
# resolves it into the set of {Agent}s that the user represents. This has the
|
7
|
+
# effect of allowing a Permit to any of those agents to take effect when
|
8
|
+
# authorizing an action by this user.
|
9
|
+
#
|
10
|
+
# This implementation only resolves the user into one agent, using the default
|
11
|
+
# conversion.
|
12
|
+
#
|
13
|
+
# To extend the set of {Agent}s resolved, implement a specialized version
|
14
|
+
# that returns an array of agents from #resolve. This customized
|
15
|
+
# implementation would typically be injected to an application-wide
|
16
|
+
# {Checkpoint::Authority}, rather than being used directly.
|
17
|
+
#
|
18
|
+
# For example, a custom resolver might add a group agent for each group that
|
19
|
+
# the user is a member of, or IP address-based geographical regions or
|
20
|
+
# organizational affiliations.
|
21
|
+
class Resolver
|
22
|
+
# Resolve an actor to a list of agents it represents.
|
23
|
+
#
|
24
|
+
# If extending or overriding, you will most likely want to either call
|
25
|
+
# super, or use the default conversion directly.
|
26
|
+
# @return [[Checkpoint::Agent]] an array of agents for this actor
|
27
|
+
# @see Checkpoint::Agent.from
|
28
|
+
def resolve(actor)
|
29
|
+
[Checkpoint::Agent.from(actor)]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint
|
4
|
+
class Agent
|
5
|
+
# An Agent::Token is an identifier object for an Agent. It
|
6
|
+
# includes a type and an identifier. A {Permit} can be granted for a Token.
|
7
|
+
# Concrete actors are resolved into a number of agents, and those agents'
|
8
|
+
# tokens will be checked for matching permits.
|
9
|
+
class Token
|
10
|
+
attr_reader :type, :id
|
11
|
+
|
12
|
+
# Create a new Agent Token representing an actor in an application.
|
13
|
+
#
|
14
|
+
# @param type [String] the application-determined type of this agent. This
|
15
|
+
# will commonly be 'user' or 'group', but may be anything that identifies
|
16
|
+
# a type of authentication attribute, such as 'account-type'. The type
|
17
|
+
# will be converted to a String if something else is supplied.
|
18
|
+
# @param id [String] the application-resolvable identifier for this agent.
|
19
|
+
# This will commonly be username or group ID, but may be any value of an
|
20
|
+
# attribute of this type used to qualify an actor (user). The id
|
21
|
+
# will be converted to a String if something else is supplied.
|
22
|
+
def initialize(type, id)
|
23
|
+
@type = type.to_s
|
24
|
+
@id = id.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String] a URI for this agent, including its type and id
|
28
|
+
def uri
|
29
|
+
"agent://#{type}/#{id}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Token] self; for convenience of taking an Agent or token
|
33
|
+
def token
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String] a token string suitable for granting or matching permits for this agent
|
38
|
+
def to_s
|
39
|
+
"#{type}:#{id}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Compare with another Agent for equality. Consider them to represent
|
43
|
+
# the same resource if `other` is an Agent, has the same type, and same id.
|
44
|
+
def eql?(other)
|
45
|
+
other.is_a?(Token) && type == other.type && id == other.id
|
46
|
+
end
|
47
|
+
|
48
|
+
alias == eql?
|
49
|
+
alias inspect uri
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|