roadforest 0.5 → 0.7
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/examples/file-management.rb +70 -58
- data/lib/roadforest/application.rb +9 -17
- data/lib/roadforest/application/dispatcher.rb +76 -9
- data/lib/roadforest/application/parameters.rb +9 -1
- data/lib/roadforest/application/path-provider.rb +30 -3
- data/lib/roadforest/application/route-adapter.rb +96 -14
- data/lib/roadforest/application/services-host.rb +21 -3
- data/lib/roadforest/augment/affordance.rb +82 -11
- data/lib/roadforest/augment/augmentation.rb +24 -6
- data/lib/roadforest/augment/augmenter.rb +12 -3
- data/lib/roadforest/authorization.rb +7 -229
- data/lib/roadforest/authorization/auth-entity.rb +26 -0
- data/lib/roadforest/authorization/authentication-chain.rb +79 -0
- data/lib/roadforest/authorization/default-authentication-store.rb +33 -0
- data/lib/roadforest/authorization/grant-builder.rb +23 -0
- data/lib/roadforest/authorization/grants-holder.rb +58 -0
- data/lib/roadforest/authorization/manager.rb +85 -0
- data/lib/roadforest/authorization/policy.rb +19 -0
- data/lib/roadforest/graph/access-manager.rb +25 -2
- data/lib/roadforest/graph/focus-list.rb +4 -0
- data/lib/roadforest/graph/graph-focus.rb +30 -13
- data/lib/roadforest/graph/nav-affordance-builder.rb +62 -0
- data/lib/roadforest/graph/normalization.rb +3 -3
- data/lib/roadforest/graph/path-vocabulary.rb +64 -0
- data/lib/roadforest/graph/post-focus.rb +5 -0
- data/lib/roadforest/graph/vocabulary.rb +4 -1
- data/lib/roadforest/http/adapters/excon.rb +4 -0
- data/lib/roadforest/http/graph-transfer.rb +17 -1
- data/lib/roadforest/http/keychain.rb +121 -33
- data/lib/roadforest/http/user-agent.rb +5 -3
- data/lib/roadforest/interface/application.rb +25 -8
- data/lib/roadforest/interface/rdf.rb +114 -15
- data/lib/roadforest/interface/utility.rb +3 -0
- data/lib/roadforest/interface/utility/backfill.rb +63 -0
- data/lib/roadforest/interface/utility/grant-list.rb +45 -0
- data/lib/roadforest/interface/utility/grant.rb +22 -0
- data/lib/roadforest/interfaces.rb +1 -0
- data/lib/roadforest/path-matcher.rb +471 -0
- data/lib/roadforest/remote-host.rb +159 -35
- data/lib/roadforest/resource/read-only.rb +23 -4
- data/lib/roadforest/server.rb +32 -3
- data/lib/roadforest/source-rigor/graph-store.rb +0 -2
- data/lib/roadforest/source-rigor/rigorous-access.rb +138 -21
- data/lib/roadforest/templates/affordance-property-values.haml +3 -0
- data/lib/roadforest/templates/rdfpost-curie.haml +1 -1
- data/lib/roadforest/test-support/matchers.rb +41 -12
- data/lib/roadforest/test-support/remote-host.rb +3 -3
- data/lib/roadforest/type-handlers/rdfa-writer/environment-decorator.rb +1 -1
- data/lib/roadforest/type-handlers/rdfa-writer/render-engine.rb +40 -27
- data/lib/roadforest/type-handlers/rdfa.rb +10 -3
- data/lib/roadforest/utility/class-registry.rb +44 -4
- data/spec/affordance-augmenter.rb +46 -19
- data/spec/affordances-flow.rb +46 -30
- data/spec/authorization.rb +16 -4
- data/spec/client.rb +22 -4
- data/spec/focus-list.rb +24 -0
- data/spec/full-integration.rb +8 -3
- data/spec/graph-store.rb +8 -0
- data/spec/keychain.rb +18 -14
- data/spec/rdf-normalization.rb +32 -6
- data/spec/update-focus.rb +36 -39
- metadata +19 -5
@@ -3,19 +3,33 @@ module RoadForest
|
|
3
3
|
#XXX Worth doing some meta to get reality checking of configs here? Better
|
4
4
|
#fail early if there's no DB configured, right?
|
5
5
|
class ServicesHost
|
6
|
+
include Graph::Normalization
|
7
|
+
|
6
8
|
def initialize
|
7
9
|
end
|
8
10
|
|
9
11
|
attr_writer :application
|
10
12
|
attr_writer :router, :canonical_host, :type_handling
|
11
13
|
attr_writer :logger, :authorization
|
14
|
+
attr_accessor :root_url
|
15
|
+
|
16
|
+
attr_accessor :default_content_engine
|
12
17
|
|
13
18
|
def canonical_host
|
14
|
-
@
|
19
|
+
@canonical_host ||= RDF::URI.intern(@root_url)
|
20
|
+
end
|
21
|
+
|
22
|
+
def augmenter
|
23
|
+
@augmenter ||= Augment::Augmenter.new(self)
|
15
24
|
end
|
16
25
|
|
17
|
-
def
|
18
|
-
@
|
26
|
+
def dispatcher
|
27
|
+
@dispatcher ||= Dispatcher.new(self)
|
28
|
+
end
|
29
|
+
alias router dispatcher
|
30
|
+
|
31
|
+
def path_provider
|
32
|
+
@path_provider ||= router.path_provider
|
19
33
|
end
|
20
34
|
|
21
35
|
def authorization
|
@@ -34,6 +48,10 @@ module RoadForest
|
|
34
48
|
end
|
35
49
|
end
|
36
50
|
|
51
|
+
def default_content_engine
|
52
|
+
@default_content_engine || ContentHandling.rdf_engine
|
53
|
+
end
|
54
|
+
|
37
55
|
alias authz authorization
|
38
56
|
end
|
39
57
|
end
|
@@ -6,7 +6,22 @@ module RoadForest
|
|
6
6
|
module Affordance
|
7
7
|
Af = Graph::Af
|
8
8
|
|
9
|
+
module GrantTokens
|
10
|
+
def each_grant_token(method, term)
|
11
|
+
grant_route = term.router.mapped_route_for_name(term.route.name, :perm, {})
|
12
|
+
term.resource.required_grants(method).each do |grant|
|
13
|
+
grant_path = grant_route.build_path(:grant_name => grant)
|
14
|
+
yield ::RDF::URI.new(canonical_uri.join(grant_path)) #XXX magical route name
|
15
|
+
end
|
16
|
+
rescue KeyError
|
17
|
+
term.resource.required_grants(method).each do |grant|
|
18
|
+
yield grant
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
9
23
|
class Remove < Augmentation
|
24
|
+
include GrantTokens
|
10
25
|
register_for_subjects
|
11
26
|
|
12
27
|
def apply(term)
|
@@ -14,11 +29,15 @@ module RoadForest
|
|
14
29
|
node = ::RDF::Node.new
|
15
30
|
yield [node, ::RDF.type, Af.Remove]
|
16
31
|
yield [node, Af.target, term.uri]
|
32
|
+
each_grant_token("DELETE", term) do |token|
|
33
|
+
yield [node, Af.authorizedBy, token]
|
34
|
+
end
|
17
35
|
end
|
18
36
|
end
|
19
37
|
end
|
20
38
|
|
21
39
|
class Links < Augmentation
|
40
|
+
include GrantTokens
|
22
41
|
register_for_subjects
|
23
42
|
register_for_objects
|
24
43
|
|
@@ -45,32 +64,84 @@ module RoadForest
|
|
45
64
|
yield [node, ::RDF.type, Af.Navigate]
|
46
65
|
yield [node, Af.target, term.uri]
|
47
66
|
end
|
67
|
+
each_grant_token("GET", term) do |token|
|
68
|
+
yield [node, Af.authorizedBy, token]
|
69
|
+
end
|
48
70
|
end
|
49
71
|
end
|
50
72
|
end
|
51
73
|
end
|
52
74
|
|
53
|
-
class
|
54
|
-
|
75
|
+
class PayloadAugmentation < Augmentation
|
76
|
+
include GrantTokens
|
77
|
+
|
78
|
+
def get_payload(resource)
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def applicable?(resource)
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
def affordance_type
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def applicable?(resource)
|
91
|
+
resource.allowed_methods.include?(http_method)
|
92
|
+
end
|
55
93
|
|
56
94
|
def apply(term)
|
57
|
-
|
95
|
+
resource = term.resource
|
96
|
+
if applicable?(resource)
|
58
97
|
node = ::RDF::Node.new
|
59
|
-
yield [node, ::RDF.type,
|
98
|
+
yield [node, ::RDF.type, affordance_type]
|
60
99
|
yield [node, Af.target, term.uri]
|
100
|
+
each_grant_token(http_method, term) do |token|
|
101
|
+
yield [node, Af.authorizedBy, token]
|
102
|
+
end
|
103
|
+
payload = get_payload(resource)
|
104
|
+
unless payload.nil?
|
105
|
+
yield [node, Af.payload, payload.root]
|
106
|
+
unless payload.graph.nil?
|
107
|
+
payload.graph.each_statement do |stmt|
|
108
|
+
yield stmt
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
61
112
|
end
|
62
113
|
end
|
63
114
|
end
|
64
115
|
|
65
|
-
class
|
116
|
+
class Update < PayloadAugmentation
|
66
117
|
register_for_subjects
|
67
118
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
119
|
+
def get_payload(resource)
|
120
|
+
resource.interface.update_payload
|
121
|
+
end
|
122
|
+
|
123
|
+
def http_method
|
124
|
+
"PUT"
|
125
|
+
end
|
126
|
+
|
127
|
+
def affordance_type
|
128
|
+
Af.Update
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Create < PayloadAugmentation
|
133
|
+
register_for_subjects
|
134
|
+
|
135
|
+
def get_payload(resource)
|
136
|
+
resource.interface.create_payload
|
137
|
+
end
|
138
|
+
|
139
|
+
def http_method
|
140
|
+
"POST"
|
141
|
+
end
|
142
|
+
|
143
|
+
def affordance_type
|
144
|
+
Af.Create
|
74
145
|
end
|
75
146
|
end
|
76
147
|
end
|
@@ -2,12 +2,30 @@ require 'roadforest/augment/augmenter'
|
|
2
2
|
module RoadForest
|
3
3
|
module Augment
|
4
4
|
class Augmentation
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class << self
|
6
|
+
def register_for_subjects
|
7
|
+
Augmenter.subject_augmentations_registry.add(self.name, self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def subject_precedes(other)
|
11
|
+
Augmenter.subject_augmentations_registry.seq(self.name, other)
|
12
|
+
end
|
13
|
+
|
14
|
+
def subject_follows(other)
|
15
|
+
Augmenter.subject_augmentations_registry.seq(other, self.name)
|
16
|
+
end
|
8
17
|
|
9
|
-
|
10
|
-
|
18
|
+
def register_for_objects
|
19
|
+
Augmenter.object_augmentations_registry.add(self.name, self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def object_precedes(other)
|
23
|
+
Augmenter.object_augmentations_registry.seq(self.name, other)
|
24
|
+
end
|
25
|
+
|
26
|
+
def object_follows(other)
|
27
|
+
Augmenter.object_augmentations_registry.seq(other, self.name)
|
28
|
+
end
|
11
29
|
end
|
12
30
|
|
13
31
|
def initialize(augmenter)
|
@@ -49,7 +67,7 @@ module RoadForest
|
|
49
67
|
|
50
68
|
def type_list
|
51
69
|
@type_list ||=
|
52
|
-
resource.content_types_provided.inject(ContentHandling::MediaTypeList.new) do |list, (type,
|
70
|
+
resource.content_types_provided.inject(ContentHandling::MediaTypeList.new) do |list, (type, _)|
|
53
71
|
list.add_header_val(type)
|
54
72
|
end
|
55
73
|
end
|
@@ -4,7 +4,18 @@ require 'roadforest/utility/class-registry'
|
|
4
4
|
module RoadForest
|
5
5
|
module Augment
|
6
6
|
class Augmenter
|
7
|
-
|
7
|
+
def initialize(services)
|
8
|
+
@services = services
|
9
|
+
end
|
10
|
+
attr_reader :services
|
11
|
+
|
12
|
+
def router
|
13
|
+
services.router
|
14
|
+
end
|
15
|
+
|
16
|
+
def canonical_uri
|
17
|
+
@canonical_uri ||= Addressable::URI.parse(services.root_url)
|
18
|
+
end
|
8
19
|
|
9
20
|
def self.subject_augmentations_registry
|
10
21
|
@subject_registry ||= Utility::ClassRegistry.new(self, "subject augmentation")
|
@@ -26,8 +37,6 @@ module RoadForest
|
|
26
37
|
end
|
27
38
|
end
|
28
39
|
|
29
|
-
attr_accessor :canonical_uri
|
30
|
-
|
31
40
|
def augment(graph)
|
32
41
|
augmenting = Augment::Process.new(graph)
|
33
42
|
|
@@ -1,231 +1,9 @@
|
|
1
|
-
require 'base64'
|
2
|
-
require 'openssl'
|
3
1
|
require 'roadforest'
|
4
|
-
require 'roadforest/utility/class-registry'
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
attr_reader :list
|
15
|
-
|
16
|
-
def add(name, params=nil)
|
17
|
-
canonical =
|
18
|
-
if params.nil?
|
19
|
-
[@salt, name]
|
20
|
-
else
|
21
|
-
[@salt, name, params.keys.sort.map do |key|
|
22
|
-
[key, params[key]]
|
23
|
-
end]
|
24
|
-
end
|
25
|
-
@list << @cache[canonical]
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
class GrantsHolder
|
30
|
-
def initialize(salt, hash_function)
|
31
|
-
@salt = salt
|
32
|
-
|
33
|
-
digester = OpenSSL::Digest.new(hash_function)
|
34
|
-
@grants_cache = Hash.new do |h, k| #XXX potential resource exhaustion here - only accumulate auth'd results
|
35
|
-
digester.reset
|
36
|
-
h[k] = digester.digest(h.inspect)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def get(key)
|
41
|
-
@grants_cache[key]
|
42
|
-
end
|
43
|
-
alias [] get
|
44
|
-
|
45
|
-
def build_grants
|
46
|
-
builder = GrantBuilder.new(@salt, self)
|
47
|
-
yield builder
|
48
|
-
return builder.list
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Manager
|
53
|
-
attr_accessor :authenticator
|
54
|
-
attr_accessor :policy
|
55
|
-
attr_reader :grants
|
56
|
-
|
57
|
-
HASH_FUNCTION = "SHA256".freeze
|
58
|
-
|
59
|
-
def initialize(salt = nil, authenticator = nil, policy = nil)
|
60
|
-
@grants = GrantsHolder.new(salt || "roadforest-insecure", HASH_FUNCTION)
|
61
|
-
|
62
|
-
@authenticator = authenticator || AuthenticationChain.new(DefaultAuthenticationStore.new)
|
63
|
-
@policy = policy || AuthorizationPolicy.new
|
64
|
-
@policy.grants_holder = @grants
|
65
|
-
end
|
66
|
-
|
67
|
-
def build_grants(&block)
|
68
|
-
@grants.build_grants(&block)
|
69
|
-
end
|
70
|
-
|
71
|
-
def challenge(options)
|
72
|
-
@authenticator.challenge(options)
|
73
|
-
end
|
74
|
-
|
75
|
-
# @returns [:public|:granted|:refused]
|
76
|
-
#
|
77
|
-
# :public means the request doesn't need authorization
|
78
|
-
# :granted means that it does need authz but the credentials passed are
|
79
|
-
# allowed to access the resource
|
80
|
-
# :refused means that the credentials passed are not allowed to access
|
81
|
-
# the resource
|
82
|
-
#
|
83
|
-
# TODO: Resource needs to add s-maxage=0 for :granted requests or public
|
84
|
-
# for :public requests to the CacheControl header
|
85
|
-
def authorization(header, required_grants)
|
86
|
-
entity = authenticator.authenticate(header)
|
87
|
-
|
88
|
-
return :refused if entity.nil?
|
89
|
-
|
90
|
-
available_grants = policy.grants_for(entity)
|
91
|
-
|
92
|
-
if required_grants.any?{|required| available_grants.include?(required)}
|
93
|
-
return :granted
|
94
|
-
else
|
95
|
-
return :refused
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
class AuthenticationChain
|
101
|
-
class Scheme
|
102
|
-
def self.registry_purpose; "authentication scheme"; end
|
103
|
-
extend Utility::ClassRegistry::Registrar
|
104
|
-
|
105
|
-
def self.register(name)
|
106
|
-
registrar.registry.add(name, self.new)
|
107
|
-
end
|
108
|
-
|
109
|
-
def authenticated_entity(credentials, store)
|
110
|
-
nil
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
class Basic < Scheme
|
115
|
-
register "Basic"
|
116
|
-
|
117
|
-
def challenge(options)
|
118
|
-
"Basic realm=\"#{options.fetch(:realm, "Roadforest App")}\""
|
119
|
-
end
|
120
|
-
|
121
|
-
def authenticated_entity(credentials, store)
|
122
|
-
username, password = Base64.decode64(credentials).split(':',2)
|
123
|
-
|
124
|
-
entity = store.by_username(username)
|
125
|
-
entity.authenticate_by_password(password)
|
126
|
-
entity
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def initialize(store)
|
131
|
-
@store = store
|
132
|
-
end
|
133
|
-
attr_reader :store
|
134
|
-
|
135
|
-
def handler_for(scheme)
|
136
|
-
Scheme.get(scheme)
|
137
|
-
rescue
|
138
|
-
nil
|
139
|
-
end
|
140
|
-
|
141
|
-
def challenge(options)
|
142
|
-
(Scheme.registry.names.map do |scheme_name|
|
143
|
-
handler_for(scheme_name).challenge(options)
|
144
|
-
end).join(", ")
|
145
|
-
end
|
146
|
-
|
147
|
-
def add_account(user,password,token)
|
148
|
-
@store.add_account(user,password,token)
|
149
|
-
end
|
150
|
-
|
151
|
-
def authenticate(header)
|
152
|
-
return nil if header.nil?
|
153
|
-
scheme, credentials = header.split(/\s+/, 2)
|
154
|
-
|
155
|
-
handler = handler_for(scheme)
|
156
|
-
return nil if handler.nil?
|
157
|
-
|
158
|
-
entity = handler.authenticated_entity(credentials, store)
|
159
|
-
return nil if entity.nil?
|
160
|
-
return nil unless entity.authenticated?
|
161
|
-
return entity
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
class AuthEntity
|
166
|
-
def initialize
|
167
|
-
@authenticated = false
|
168
|
-
end
|
169
|
-
attr_accessor :username, :password, :token
|
170
|
-
|
171
|
-
def authenticated?
|
172
|
-
!!@authenticated
|
173
|
-
end
|
174
|
-
|
175
|
-
def authenticate_by_password(password)
|
176
|
-
@authenticated = (!password.nil? and password == @password)
|
177
|
-
end
|
178
|
-
|
179
|
-
def authenticate_by_token(token)
|
180
|
-
@authenticated = (!token.nil? and token == @token)
|
181
|
-
end
|
182
|
-
|
183
|
-
def authenticate!
|
184
|
-
@authenticated = true
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
class DefaultAuthenticationStore
|
189
|
-
def initialize
|
190
|
-
@accounts = []
|
191
|
-
end
|
192
|
-
|
193
|
-
def build_entity(account)
|
194
|
-
return nil if account.nil?
|
195
|
-
AuthEntity.new.tap do |entity|
|
196
|
-
entity.username = account[0]
|
197
|
-
entity.password = account[1]
|
198
|
-
entity.token = account[2]
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def add_account(user, password, token)
|
203
|
-
@accounts << [user, password, token]
|
204
|
-
end
|
205
|
-
|
206
|
-
def by_username(username)
|
207
|
-
account = @accounts.find{|account| account[0] == username }
|
208
|
-
build_entity(account)
|
209
|
-
end
|
210
|
-
|
211
|
-
def by_token(token)
|
212
|
-
account = @accounts.find{|account| account[2] == token }
|
213
|
-
build_entity(account)
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
class AuthorizationPolicy
|
218
|
-
attr_accessor :grants_holder
|
219
|
-
|
220
|
-
def build_grants(&block)
|
221
|
-
grants_holder.build_grants(&block)
|
222
|
-
end
|
223
|
-
|
224
|
-
def grants_for(entity)
|
225
|
-
build_grants do |builder|
|
226
|
-
builder.add(:admin)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
3
|
+
require 'roadforest/authorization/grant-builder'
|
4
|
+
require 'roadforest/authorization/grants-holder'
|
5
|
+
require 'roadforest/authorization/authentication-chain'
|
6
|
+
require 'roadforest/authorization/auth-entity'
|
7
|
+
require 'roadforest/authorization/default-authentication-store'
|
8
|
+
require 'roadforest/authorization/policy'
|
9
|
+
require 'roadforest/authorization/manager'
|