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