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
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'roadforest/graph'
|
2
|
+
|
3
|
+
module RoadForest
|
4
|
+
module Graph
|
5
|
+
class NavAffordanceBuilder
|
6
|
+
def initialize(focus, path_provider)
|
7
|
+
@focus, @path_provider = focus, path_provider
|
8
|
+
end
|
9
|
+
attr_reader :path_provider, :focus
|
10
|
+
|
11
|
+
def to(name, params=nil)
|
12
|
+
route = path_provider.route_for_name(name)
|
13
|
+
|
14
|
+
pattern = iri_template(route, params)
|
15
|
+
|
16
|
+
node = ::RDF::Node.new
|
17
|
+
tmpl = ::RDF::Node.new
|
18
|
+
focus << [ node, ::RDF.type, Af.Navigate ]
|
19
|
+
focus << [ node, Af.target, tmpl ]
|
20
|
+
focus << [ tmpl, Af.pattern, pattern ]
|
21
|
+
end
|
22
|
+
|
23
|
+
def iri_template(route, params)
|
24
|
+
klass = route.interface_class
|
25
|
+
return if klass.nil?
|
26
|
+
|
27
|
+
variables = klass.path_params
|
28
|
+
|
29
|
+
params ||= {}
|
30
|
+
params = params.dup
|
31
|
+
|
32
|
+
variables -= params.keys
|
33
|
+
|
34
|
+
path_spec = route.resolve_path_spec(params)
|
35
|
+
|
36
|
+
path = path_provider.services.canonical_host.to_s.sub(%r{/$}, '') +
|
37
|
+
path_spec.map do |segment|
|
38
|
+
case segment
|
39
|
+
when Symbol
|
40
|
+
variables.delete(segment)
|
41
|
+
"{/#{segment}}"
|
42
|
+
when '*'
|
43
|
+
"{/extra*}"
|
44
|
+
else
|
45
|
+
"/" + segment.to_s
|
46
|
+
end
|
47
|
+
end.join("")
|
48
|
+
|
49
|
+
unless params.empty?
|
50
|
+
path += "?" + params.map do |key, value|
|
51
|
+
[key, value].join("=")
|
52
|
+
end.join("&")
|
53
|
+
end
|
54
|
+
|
55
|
+
unless variables.empty?
|
56
|
+
path += "{?#{variables.join(",")}}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -6,7 +6,7 @@ module RoadForest::Graph
|
|
6
6
|
Vocabs = {}
|
7
7
|
Vocabs["rdf"] = RDF
|
8
8
|
|
9
|
-
def normalize_statement(subject, predicate, object, context)
|
9
|
+
def normalize_statement(subject, predicate, object, context=nil)
|
10
10
|
subject = normalize_resource(subject) || RDF::Node.new
|
11
11
|
predicate = normalize_uri(predicate)
|
12
12
|
object = normalize_term(object) || RDF::Node.new
|
@@ -43,7 +43,7 @@ module RoadForest::Graph
|
|
43
43
|
def normalize_context(from)
|
44
44
|
case from
|
45
45
|
when Array
|
46
|
-
from = expand_curie(from)
|
46
|
+
from = uri(expand_curie(from))
|
47
47
|
when RDF::URI, Addressable::URI, String
|
48
48
|
from = uri(from)
|
49
49
|
else
|
@@ -107,7 +107,7 @@ module RoadForest::Graph
|
|
107
107
|
|
108
108
|
def expand_curie_pair(prefix, property)
|
109
109
|
vocab = Vocabs.fetch(prefix) do
|
110
|
-
vocab = RDF::Vocabulary.
|
110
|
+
vocab = RDF::Vocabulary.detect do |vocab|
|
111
111
|
unless vocab.__prefix__.is_a? RDF::URI
|
112
112
|
Vocabs[vocab.__prefix__.to_s] = vocab
|
113
113
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# This file generated automatically using vocab-fetch from path.ttl
|
2
|
+
require 'rdf'
|
3
|
+
module RDF
|
4
|
+
class RoadForest::Graph::Path < StrictVocabulary("http://judsonlester.info/rdf-vocab/path#")
|
5
|
+
|
6
|
+
# Class definitions
|
7
|
+
property :Literal
|
8
|
+
property :MultipleStep, :comment =>
|
9
|
+
%(A step that may appear more than once on a subject)
|
10
|
+
property :RepeatingStep, :comment =>
|
11
|
+
%(A step that repeats - the subject of a matching step become
|
12
|
+
the subject of a new match)
|
13
|
+
property :Root, :comment =>
|
14
|
+
%(The starting point of a path pattern)
|
15
|
+
property :Step, :comment =>
|
16
|
+
%(Base class for path steps)
|
17
|
+
property :Target, :comment =>
|
18
|
+
%(A target for the pattern)
|
19
|
+
|
20
|
+
# Property definitions
|
21
|
+
property :after, :comment =>
|
22
|
+
%(Constrains the matching of this literal to values that come
|
23
|
+
after the subject value in some order.)
|
24
|
+
property :before, :comment =>
|
25
|
+
%(Constrains the matching of this literal to values that come
|
26
|
+
before the subject value in some order.)
|
27
|
+
property :constraint, :comment =>
|
28
|
+
%(Description of constraints on values that can match this
|
29
|
+
Literal.)
|
30
|
+
property :defaultValue
|
31
|
+
property :forward, :comment =>
|
32
|
+
%(Indicates that the subject of the matched statement matches
|
33
|
+
the subject, and the predicate of the matched statement
|
34
|
+
matches the predicate.)
|
35
|
+
property :is, :comment =>
|
36
|
+
%(The target of this step has exactly this value.)
|
37
|
+
property :label
|
38
|
+
property :maxMulti, :comment =>
|
39
|
+
%(Limits the number of times a repeating step may repeat -
|
40
|
+
otherwise a repeating step is assumed to have no limit to
|
41
|
+
repetitions.)
|
42
|
+
property :maxRepeat, :comment =>
|
43
|
+
%(Limits the number of times a repeating step may repeat -
|
44
|
+
otherwise a repeating step is assumed to have no limit to
|
45
|
+
repetitions.)
|
46
|
+
property :minMulti, :comment =>
|
47
|
+
%(Limits the number of times a repeating step may repeat -
|
48
|
+
otherwise a repeating step is assumed allow 0 repetions.)
|
49
|
+
property :minRepeat, :comment =>
|
50
|
+
%(Limits the number of times a repeating step may repeat -
|
51
|
+
otherwise a repeating step is assumed allow 0 repetions.)
|
52
|
+
property :name
|
53
|
+
property :order, :comment =>
|
54
|
+
%(The subject resource describes the order in which to consider
|
55
|
+
values of this Literal.)
|
56
|
+
property :predicate, :comment =>
|
57
|
+
%(The property of a statement that matches this step)
|
58
|
+
property :reverse, :comment =>
|
59
|
+
%(Indicates that the subject of the matched statement matches
|
60
|
+
the predicate, and the predicate of the matched statement
|
61
|
+
matches the subject.)
|
62
|
+
property :type
|
63
|
+
end
|
64
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'roadforest/graph'
|
2
2
|
require 'rdf'
|
3
3
|
|
4
|
+
require 'roadforest/graph/path-vocabulary'
|
5
|
+
|
4
6
|
module RoadForest::Graph
|
5
7
|
module Vocabulary
|
6
8
|
class RF < ::RDF::Vocabulary("http://lrdesign.com/graph/roadforest#")
|
@@ -10,7 +12,7 @@ module RoadForest::Graph
|
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
13
|
-
class Af < ::RDF::StrictVocabulary("http://judsonlester.info/affordance#")
|
15
|
+
class Af < ::RDF::StrictVocabulary("http://judsonlester.info/rdf-vocab/affordance#")
|
14
16
|
|
15
17
|
# Class definitions
|
16
18
|
property :Affordance, :comment =>
|
@@ -57,6 +59,7 @@ module RoadForest::Graph
|
|
57
59
|
the tokens be defreferenceable, and that they be accessible
|
58
60
|
iff the user is authorized to activate the affordance in
|
59
61
|
question.)
|
62
|
+
property :grants
|
60
63
|
property :controlName, :comment =>
|
61
64
|
%(Valid values are limited per application. Examples include
|
62
65
|
'Media-Type', 'Encoding' or 'EntityTag')
|
@@ -5,9 +5,10 @@ module RoadForest
|
|
5
5
|
module HTTP
|
6
6
|
class GraphTransfer
|
7
7
|
attr_writer :type_handling
|
8
|
-
attr_accessor :user_agent
|
8
|
+
attr_accessor :user_agent, :trace
|
9
9
|
|
10
10
|
def initialize(user_agent)
|
11
|
+
@trace = false
|
11
12
|
@user_agent = user_agent
|
12
13
|
@type_preferences = Hash.new{|h,k| k.nil? ? "*/*" : h[nil]}
|
13
14
|
end
|
@@ -28,6 +29,8 @@ module RoadForest
|
|
28
29
|
headers = {"Accept" => type_handling.parsers.types.accept_header}
|
29
30
|
body = nil
|
30
31
|
|
32
|
+
trace_graph("OUT", graph)
|
33
|
+
|
31
34
|
if(%w{POST PUT PATCH}.include? method.upcase)
|
32
35
|
content_type = best_type_for(url)
|
33
36
|
renderer = type_handling.choose_renderer(content_type)
|
@@ -49,6 +52,17 @@ module RoadForest
|
|
49
52
|
retry
|
50
53
|
end
|
51
54
|
|
55
|
+
def trace_graph(tag, graph)
|
56
|
+
return unless @trace
|
57
|
+
require 'rdf/turtle'
|
58
|
+
@trace = $stderr unless @trace.respond_to?(:puts)
|
59
|
+
@trace.puts "<#{tag}"
|
60
|
+
if graph.respond_to?(:dump)
|
61
|
+
@trace.puts graph.dump(:ntriples, :standard_prefixes => true, :prefixes => { "af" => "http://judsonlester.info/affordance#"})
|
62
|
+
end
|
63
|
+
@trace.puts "#{tag}>"
|
64
|
+
end
|
65
|
+
|
52
66
|
def type_handling
|
53
67
|
@type_handling || ContentHandling.rdf_engine
|
54
68
|
end
|
@@ -67,6 +81,8 @@ module RoadForest
|
|
67
81
|
parser = type_handling.choose_parser(response.headers["Content-Type"])
|
68
82
|
graph = parser.to_graph(url, response.body_string)
|
69
83
|
|
84
|
+
trace_graph("IN", graph)
|
85
|
+
|
70
86
|
response = GraphResponse.new(url, response)
|
71
87
|
response.graph = graph
|
72
88
|
return response
|
@@ -3,76 +3,164 @@ require 'addressable/uri'
|
|
3
3
|
|
4
4
|
module RoadForest
|
5
5
|
module HTTP
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
class BasicCredentials < Struct.new(:user, :secret)
|
7
|
+
def header_value
|
8
|
+
"Basic #{Base64.strict_encode64("#{user}:#{secret}")}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
YAML.add_tag("!basic", BasicCredentials)
|
12
|
+
|
13
|
+
class CredentialSource
|
14
|
+
def canonical_root(url)
|
15
|
+
url = Addressable::URI.parse(url)
|
16
|
+
url.path = "/"
|
17
|
+
url.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# @returns {BasicCredentials}
|
21
|
+
# @returns nil to indicate no further credentials available
|
22
|
+
def respond_to_challenge(url, realm, attempt)
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class PreparedCredentialSource < CredentialSource
|
28
|
+
def initialize
|
29
|
+
@for_url = Hash.new{|h,k| h[k] = []}
|
30
|
+
end
|
31
|
+
|
32
|
+
def add(url, user, secret)
|
33
|
+
creds = BasicCredentials.new(user, secret)
|
34
|
+
add_credentials(url, creds)
|
12
35
|
end
|
13
36
|
|
37
|
+
def add_credentials(url, creds)
|
38
|
+
@for_url[canonical_root(url)] << creds
|
39
|
+
end
|
40
|
+
|
41
|
+
def respond_to_challenge(url, realm, attempt)
|
42
|
+
@for_url[canonical_root(url)].fetch(attempt)
|
43
|
+
rescue IndexError
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#Manages user credentials for HTTP Basic auth
|
49
|
+
class Keychain
|
50
|
+
ATTEMPT_LIMIT = 5
|
14
51
|
def initialize
|
15
52
|
@realm_for_url = {}
|
16
53
|
@with_realm = {}
|
54
|
+
@sources = []
|
55
|
+
@source_enums = Hash.new{|h,k| @sources.each}
|
56
|
+
@attempt_enums = Hash.new{|h,k| (0...ATTEMPT_LIMIT).each}
|
17
57
|
end
|
18
58
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
59
|
+
def add_source(source)
|
60
|
+
@sources << source
|
61
|
+
@source_enums.clear
|
62
|
+
@attempt_enums.clear
|
22
63
|
end
|
23
64
|
|
24
|
-
def
|
25
|
-
if url.to_s[-1] != "/"
|
26
|
-
url << "/"
|
27
|
-
end
|
28
|
-
@realm_for_url[url.to_s] = realm
|
29
|
-
|
65
|
+
def canonical_root(url)
|
30
66
|
url = Addressable::URI.parse(url)
|
31
67
|
url.path = "/"
|
32
|
-
|
68
|
+
url.fragment = nil
|
69
|
+
url.query = nil
|
70
|
+
url.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def stripped_url(url)
|
74
|
+
url = Addressable::URI.parse(url)
|
75
|
+
url.fragment = nil
|
76
|
+
url.query = nil
|
77
|
+
url
|
33
78
|
end
|
34
79
|
|
35
80
|
BASIC_SCHEME = /basic\s+realm=(?<q>['"])(?<realm>(?:(?!['"]).)*)\k<q>/i
|
36
81
|
|
37
82
|
def challenge_response(url, challenge)
|
83
|
+
url = stripped_url(url).to_s
|
84
|
+
#Future note: the RFC means that the creds selection mechanics are
|
85
|
+
#valid for all HTTP WWW-Authenticate reponses
|
38
86
|
if (match = BASIC_SCHEME.match(challenge)).nil?
|
39
87
|
return nil
|
40
88
|
end
|
41
89
|
realm = match[:realm]
|
90
|
+
@realm_for_url[url] = realm
|
42
91
|
|
43
|
-
|
92
|
+
cached_response(canonical_root(url), realm) || missing_credentials(url, realm)
|
44
93
|
end
|
45
94
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
creds = missing_credentials(url, realm)
|
52
|
-
unless creds.nil?
|
53
|
-
add_credentials(url, creds, realm)
|
54
|
-
end
|
55
|
-
end
|
95
|
+
def preemptive_response(url)
|
96
|
+
realm = realm_for_url(url)
|
97
|
+
url = canonical_root(url)
|
98
|
+
return cached_response(url, realm)
|
99
|
+
end
|
56
100
|
|
101
|
+
def cached_response(url, realm)
|
102
|
+
creds = credentials(url, realm)
|
57
103
|
return nil if creds.nil?
|
58
|
-
|
59
104
|
return creds.header_value
|
60
105
|
end
|
61
106
|
|
107
|
+
def credentials_for(url)
|
108
|
+
realm = realm_for_url(url)
|
109
|
+
url = canonical_root(url)
|
110
|
+
credentials(url, realm)
|
111
|
+
end
|
112
|
+
|
113
|
+
def credentials(url, realm = nil)
|
114
|
+
@with_realm[[url, realm]]
|
115
|
+
end
|
116
|
+
|
62
117
|
def missing_credentials(url, realm)
|
118
|
+
loop do
|
119
|
+
attempt = next_attempt(url, realm)
|
120
|
+
creds = current_source(url, realm).respond_to_challenge(url, realm, attempt)
|
121
|
+
if creds.nil?
|
122
|
+
next_source(url, realm)
|
123
|
+
else
|
124
|
+
@with_realm[[canonical_root(url), realm]] = creds
|
125
|
+
return creds.header_value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
return nil
|
129
|
+
rescue StopIteration
|
63
130
|
nil
|
64
131
|
end
|
65
132
|
|
66
|
-
def
|
67
|
-
|
133
|
+
def forget(url, realm)
|
134
|
+
@with_realm.delete([url, realm])
|
135
|
+
end
|
136
|
+
|
137
|
+
def current_source(url, realm)
|
138
|
+
@source_enums[[url, realm]].peek
|
139
|
+
end
|
140
|
+
|
141
|
+
def next_source(url, realm)
|
142
|
+
@attempt_enums.delete([url, realm])
|
143
|
+
@source_enums[[url, realm]].next
|
144
|
+
rescue StopIteration
|
145
|
+
@source_enums.delete([url, realm])
|
146
|
+
raise
|
147
|
+
end
|
68
148
|
|
149
|
+
def next_attempt(url, realm)
|
150
|
+
@attempt_enums[[url, realm]].next
|
151
|
+
rescue StopIteration
|
152
|
+
next_source(url, realm)
|
153
|
+
retry
|
154
|
+
end
|
155
|
+
|
156
|
+
def realm_for_url(url)
|
157
|
+
url = stripped_url(url)
|
69
158
|
while (realm = @realm_for_url[url.to_s]).nil?
|
70
159
|
new_url = url.join("..")
|
71
|
-
|
160
|
+
return realm if new_url == url
|
72
161
|
url = new_url
|
73
162
|
end
|
74
|
-
|
75
|
-
return response(url, realm)
|
163
|
+
return realm || :default
|
76
164
|
end
|
77
165
|
end
|
78
166
|
end
|