roadforest 0.5 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|