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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/examples/file-management.rb +70 -58
  3. data/lib/roadforest/application.rb +9 -17
  4. data/lib/roadforest/application/dispatcher.rb +76 -9
  5. data/lib/roadforest/application/parameters.rb +9 -1
  6. data/lib/roadforest/application/path-provider.rb +30 -3
  7. data/lib/roadforest/application/route-adapter.rb +96 -14
  8. data/lib/roadforest/application/services-host.rb +21 -3
  9. data/lib/roadforest/augment/affordance.rb +82 -11
  10. data/lib/roadforest/augment/augmentation.rb +24 -6
  11. data/lib/roadforest/augment/augmenter.rb +12 -3
  12. data/lib/roadforest/authorization.rb +7 -229
  13. data/lib/roadforest/authorization/auth-entity.rb +26 -0
  14. data/lib/roadforest/authorization/authentication-chain.rb +79 -0
  15. data/lib/roadforest/authorization/default-authentication-store.rb +33 -0
  16. data/lib/roadforest/authorization/grant-builder.rb +23 -0
  17. data/lib/roadforest/authorization/grants-holder.rb +58 -0
  18. data/lib/roadforest/authorization/manager.rb +85 -0
  19. data/lib/roadforest/authorization/policy.rb +19 -0
  20. data/lib/roadforest/graph/access-manager.rb +25 -2
  21. data/lib/roadforest/graph/focus-list.rb +4 -0
  22. data/lib/roadforest/graph/graph-focus.rb +30 -13
  23. data/lib/roadforest/graph/nav-affordance-builder.rb +62 -0
  24. data/lib/roadforest/graph/normalization.rb +3 -3
  25. data/lib/roadforest/graph/path-vocabulary.rb +64 -0
  26. data/lib/roadforest/graph/post-focus.rb +5 -0
  27. data/lib/roadforest/graph/vocabulary.rb +4 -1
  28. data/lib/roadforest/http/adapters/excon.rb +4 -0
  29. data/lib/roadforest/http/graph-transfer.rb +17 -1
  30. data/lib/roadforest/http/keychain.rb +121 -33
  31. data/lib/roadforest/http/user-agent.rb +5 -3
  32. data/lib/roadforest/interface/application.rb +25 -8
  33. data/lib/roadforest/interface/rdf.rb +114 -15
  34. data/lib/roadforest/interface/utility.rb +3 -0
  35. data/lib/roadforest/interface/utility/backfill.rb +63 -0
  36. data/lib/roadforest/interface/utility/grant-list.rb +45 -0
  37. data/lib/roadforest/interface/utility/grant.rb +22 -0
  38. data/lib/roadforest/interfaces.rb +1 -0
  39. data/lib/roadforest/path-matcher.rb +471 -0
  40. data/lib/roadforest/remote-host.rb +159 -35
  41. data/lib/roadforest/resource/read-only.rb +23 -4
  42. data/lib/roadforest/server.rb +32 -3
  43. data/lib/roadforest/source-rigor/graph-store.rb +0 -2
  44. data/lib/roadforest/source-rigor/rigorous-access.rb +138 -21
  45. data/lib/roadforest/templates/affordance-property-values.haml +3 -0
  46. data/lib/roadforest/templates/rdfpost-curie.haml +1 -1
  47. data/lib/roadforest/test-support/matchers.rb +41 -12
  48. data/lib/roadforest/test-support/remote-host.rb +3 -3
  49. data/lib/roadforest/type-handlers/rdfa-writer/environment-decorator.rb +1 -1
  50. data/lib/roadforest/type-handlers/rdfa-writer/render-engine.rb +40 -27
  51. data/lib/roadforest/type-handlers/rdfa.rb +10 -3
  52. data/lib/roadforest/utility/class-registry.rb +44 -4
  53. data/spec/affordance-augmenter.rb +46 -19
  54. data/spec/affordances-flow.rb +46 -30
  55. data/spec/authorization.rb +16 -4
  56. data/spec/client.rb +22 -4
  57. data/spec/focus-list.rb +24 -0
  58. data/spec/full-integration.rb +8 -3
  59. data/spec/graph-store.rb +8 -0
  60. data/spec/keychain.rb +18 -14
  61. data/spec/rdf-normalization.rb +32 -6
  62. data/spec/update-focus.rb +36 -39
  63. metadata +19 -5
@@ -0,0 +1,26 @@
1
+ module RoadForest
2
+ module Authorization
3
+ class AuthEntity
4
+ def initialize
5
+ @authenticated = false
6
+ end
7
+ attr_accessor :username, :password, :token
8
+
9
+ def authenticated?
10
+ !!@authenticated
11
+ end
12
+
13
+ def authenticate_by_password(password)
14
+ @authenticated = (!password.nil? and password == @password)
15
+ end
16
+
17
+ def authenticate_by_token(token)
18
+ @authenticated = (!token.nil? and token == @token)
19
+ end
20
+
21
+ def authenticate!
22
+ @authenticated = true
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,79 @@
1
+ require 'base64'
2
+ require 'roadforest/utility/class-registry'
3
+ module RoadForest
4
+ module Authorization
5
+ class AuthenticationChain
6
+ class Scheme
7
+ def self.registry_purpose; "authentication scheme"; end
8
+ extend Utility::ClassRegistry::Registrar
9
+
10
+ def self.register(name)
11
+ registrar.registry.add(name, self.new)
12
+ end
13
+
14
+ def authenticated_entity(credentials, store)
15
+ nil
16
+ end
17
+ end
18
+
19
+ class Basic < Scheme
20
+ register "Basic"
21
+
22
+ def challenge(options)
23
+ "Basic realm=\"#{options.fetch(:realm, "Roadforest App")}\""
24
+ end
25
+
26
+ def authenticated_entity(credentials, store)
27
+ username, password = Base64.decode64(credentials).split(':',2)
28
+
29
+ entity = store.by_username(username)
30
+ entity.authenticate_by_password(password)
31
+ entity
32
+ end
33
+ end
34
+
35
+ def initialize(store)
36
+ @store = store
37
+ end
38
+ attr_reader :store
39
+
40
+ def handler_for(scheme)
41
+ Scheme.get(scheme)
42
+ rescue
43
+ nil
44
+ end
45
+
46
+ def challenge(options)
47
+ (Scheme.registry.names.map do |scheme_name|
48
+ handler_for(scheme_name).challenge(options)
49
+ end).join(", ")
50
+ end
51
+
52
+ def add_account(user,password,token)
53
+ @store.add_account(user,password,token)
54
+ end
55
+
56
+ def authenticate(request)
57
+ if request.respond_to?(:client_cert)
58
+ subject = request.client_cert.subject
59
+ name = subject.to_a.find{|entry| entry[0] == "CN"}[1]
60
+ entity = @store.by_username(name)
61
+ entity.authenticate!
62
+ return entity
63
+ end
64
+
65
+ header = request.headers["Authorization"]
66
+ return nil if header.nil?
67
+ scheme, credentials = header.split(/\s+/, 2)
68
+
69
+ handler = handler_for(scheme)
70
+ return nil if handler.nil?
71
+
72
+ entity = handler.authenticated_entity(credentials, store)
73
+ return nil if entity.nil?
74
+ return nil unless entity.authenticated?
75
+ return entity
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,33 @@
1
+ require 'roadforest/authorization/auth-entity'
2
+ module RoadForest
3
+ module Authorization
4
+ class DefaultAuthenticationStore
5
+ def initialize
6
+ @accounts = []
7
+ end
8
+
9
+ def build_entity(account)
10
+ return nil if account.nil?
11
+ AuthEntity.new.tap do |entity|
12
+ entity.username = account[0]
13
+ entity.password = account[1]
14
+ entity.token = account[2]
15
+ end
16
+ end
17
+
18
+ def add_account(user, password, token)
19
+ @accounts << [user, password, token]
20
+ end
21
+
22
+ def by_username(username)
23
+ account = @accounts.find{|account| account[0] == username }
24
+ build_entity(account)
25
+ end
26
+
27
+ def by_token(token)
28
+ account = @accounts.find{|account| account[2] == token }
29
+ build_entity(account)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ module RoadForest
2
+ module Authorization
3
+ class GrantBuilder
4
+ def initialize(cache)
5
+ @cache = cache
6
+ @list = []
7
+ end
8
+ attr_reader :list
9
+
10
+ def add(name, params=nil)
11
+ canonical =
12
+ if params.nil?
13
+ [name]
14
+ else
15
+ [name, params.keys.sort.map do |key|
16
+ [key, params[key]]
17
+ end]
18
+ end
19
+ @list << @cache[canonical]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,58 @@
1
+ require 'roadforest/authorization/grant-builder'
2
+ module RoadForest
3
+ module Authorization
4
+ # Caches the obfuscated tokens used to identify permission grants
5
+ class GrantsHolder
6
+ def initialize(salt, hash_function)
7
+ digester = OpenSSL::HMAC.new(salt, hash_function)
8
+ @conceal = true
9
+ @grants_cache = Hash.new do |h, k| #XXX potential resource exhaustion here - only accumulate auth'd results
10
+ if conceal
11
+ digester.reset
12
+ digester << token_for(k)
13
+ h[k] = digester.hexdigest
14
+ else
15
+ token_for(k)
16
+ end
17
+ end
18
+ end
19
+ attr_accessor :conceal
20
+
21
+ #For use in URIs, per RFC3986:
22
+ #Cannot use: ":/?#[]@!$&'()*+;="
23
+ #Percent encoding uses %
24
+ #Can use: ".,$^*_-|<>~`"
25
+ #Grants are of the form [:name, [:key, value]*]
26
+ def token_for(grant)
27
+ name, attrs = *grant
28
+ attrs = (attrs || []).map{|pair| group(pair, "_", "~")}
29
+ percent_encode(group([name] + attrs, ".", "-"))
30
+ end
31
+
32
+ def group(list, sep, replace)
33
+ list.map{|part| part.to_s.gsub(sep, replace)}.join(sep)
34
+ end
35
+
36
+ PERCENT_ENCODINGS = Hash.new do |h,k|
37
+ h[k] = k.force_encoding("US-ASCII").getbyte(0).to_s(16)
38
+ end
39
+
40
+ def percent_encode(string)
41
+ string.gsub(%r|[\[\]:/?#@!$&'()*+;=]|) do |match|
42
+ PERCENT_ENCODINGS[match]
43
+ end
44
+ end
45
+
46
+ def get(key)
47
+ @grants_cache[key]
48
+ end
49
+ alias [] get
50
+
51
+ def build_grants
52
+ builder = GrantBuilder.new(self)
53
+ yield builder
54
+ return builder.list
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,85 @@
1
+ require 'roadforest/authorization/authentication-chain'
2
+ require 'roadforest/authorization/grants-holder'
3
+ require 'roadforest/authorization/default-authentication-store'
4
+ require 'roadforest/authorization/policy'
5
+
6
+ module RoadForest
7
+ module Authorization
8
+ # The root of the RoadForest authorization scheme.
9
+
10
+ # Resources describe a set of permissions that are allowed to access them,
11
+ # on a per-method case.
12
+ #
13
+ # An overall Policy object provides permission grants to authenticated
14
+ # entities (typically users, but could be e.g. applications acting on their
15
+ # behalf)
16
+ #
17
+ # The ultimate grant/refuse decision comes down to: is there a shared
18
+ # permission in the list required by the resource and those granted to the
19
+ # entity.
20
+ #
21
+ # Permissions have a name and an optional set of parameters, and can be
22
+ # referred to as such within the application on the server. They're stored
23
+ # as digests of those names, which should be safe to communicate to the
24
+ # user application, which can make interaction decisions based on the
25
+ # permissions presented.
26
+ #
27
+ # The default ServicesHost exposes a Manager as #authz
28
+ class Manager
29
+ attr_accessor :authenticator
30
+ attr_accessor :policy
31
+ attr_reader :grants
32
+
33
+ HASH_FUNCTION = "SHA256".freeze
34
+
35
+ def initialize(salt = nil, authenticator = nil, policy = nil)
36
+ #XXX consider launch-time randomized salt
37
+ @grants = GrantsHolder.new(salt || "roadforest-insecure", HASH_FUNCTION)
38
+
39
+ @store = DefaultAuthenticationStore.new
40
+ @authenticator = authenticator || AuthenticationChain.new(@store)
41
+ @policy = policy || Policy.new
42
+ @policy.grants_holder = @grants
43
+ end
44
+
45
+ def cleartext_grants!
46
+ @grants.conceal = false
47
+ end
48
+
49
+ def build_grants(&block)
50
+ @grants.build_grants(&block)
51
+ end
52
+
53
+ def challenge(options)
54
+ @authenticator.challenge(options)
55
+ end
56
+
57
+ # @returns [:public|:granted|:refused]
58
+ #
59
+ # :public means the request doesn't need authorization
60
+ # :granted means that it does need authz but the credentials passed are
61
+ # allowed to access the resource
62
+ # :refused means that the credentials passed are not allowed to access
63
+ # the resource
64
+ #
65
+ # TODO: Resource needs to add s-maxage=0 for :granted requests or public
66
+ # for :public requests to the CacheControl header
67
+ def authorization(request, required_grants)
68
+ entity = nil
69
+ if entity.nil?
70
+ entity = authenticator.authenticate(request)
71
+ end
72
+
73
+ return :refused if entity.nil?
74
+
75
+ available_grants = policy.grants_for(entity)
76
+
77
+ if required_grants.any?{|required| available_grants.include?(required)}
78
+ return :granted
79
+ else
80
+ return :refused
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,19 @@
1
+ module RoadForest
2
+ module Authorization
3
+ # Responsible to assigning particular permission grants to entities. There
4
+ # should be one subclass of Policy per application, ideally.
5
+ class Policy
6
+ attr_accessor :grants_holder
7
+
8
+ def build_grants(&block)
9
+ grants_holder.build_grants(&block)
10
+ end
11
+
12
+ def grants_for(entity)
13
+ build_grants do |builder|
14
+ builder.add(:admin)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -14,6 +14,9 @@ module RoadForest::Graph
14
14
  @resource = normalize_context(resource)
15
15
  end
16
16
 
17
+ def reset
18
+ end
19
+
17
20
  def dup
18
21
  other = self.class.allocate
19
22
  other.resource = self.resource
@@ -29,6 +32,10 @@ module RoadForest::Graph
29
32
  relevant_prefixes_for_graph(origin_graph)
30
33
  end
31
34
 
35
+ def each_statement(&block)
36
+ origin_graph.each_statement(&block)
37
+ end
38
+
32
39
  def each(&block)
33
40
  origin_graph.each(&block)
34
41
  end
@@ -53,14 +60,18 @@ module RoadForest::Graph
53
60
  end
54
61
 
55
62
  class WriteManager < ReadOnlyManager
63
+ include ::RDF::Writable
64
+
56
65
  def insert(statement)
57
- statement[3] = resource
66
+ destination_graph.insert(statement)
67
+ end
68
+
69
+ def insert_statement(statement)
58
70
  destination_graph.insert(statement)
59
71
  end
60
72
 
61
73
  def delete(statement)
62
74
  statement = RDF::Query::Pattern.from(statement)
63
- statement.context = resource
64
75
  destination_graph.delete(statement)
65
76
  end
66
77
  end
@@ -70,6 +81,11 @@ module RoadForest::Graph
70
81
 
71
82
  alias destination_graph target_graph
72
83
 
84
+ def reset
85
+ @target_graph ||= ::RDF::Repository.new
86
+ @target_graph.clear
87
+ end
88
+
73
89
  def dup
74
90
  other = super
75
91
  other.target_graph = self.target_graph
@@ -79,6 +95,13 @@ module RoadForest::Graph
79
95
  def relevant_prefixes
80
96
  super.merge(relevant_prefixes_for_graph(destination_graph))
81
97
  end
98
+
99
+ def each_target
100
+ destination_graph.each_context do |context|
101
+ graph = ::RDF::Graph.new(context, :data => target_graph)
102
+ yield(context, graph)
103
+ end
104
+ end
82
105
  end
83
106
 
84
107
  class CopyManager < SplitManager
@@ -44,6 +44,10 @@ module RoadForest::Graph
44
44
  alias << append
45
45
 
46
46
  def append_node(subject=nil)
47
+ if empty? and self.subject == RDF.nil
48
+ raise "Attempted to append #{subject} to an empty list (remove the list and replace it)"
49
+ end
50
+
47
51
  base_node.create_node(subject) do |node|
48
52
  append(node.subject)
49
53
  yield node if block_given?
@@ -45,15 +45,8 @@ module RoadForest::Graph
45
45
  end
46
46
 
47
47
  class GraphFocus
48
- #XXX Any changes to this class heirarchy or to ContextFascade should start
49
- #with a refactor like:
50
- # Reduce this to the single-node API ([] []=)
51
- # Change the ContextFascade into a family of classes (RO, RW, Update)
52
48
  include Normalization
53
49
 
54
- #attr_accessor :source_graph, :target_graph, :subject, :root_url,
55
- #:source_rigor
56
-
57
50
  attr_accessor :subject, :access_manager
58
51
 
59
52
  alias rdf subject
@@ -63,6 +56,10 @@ module RoadForest::Graph
63
56
  self.subject = subject unless subject.nil?
64
57
  end
65
58
 
59
+ def reset
60
+ access_manager.reset
61
+ end
62
+
66
63
  def root_url
67
64
  @access_manager.resource
68
65
  end
@@ -111,6 +108,14 @@ module RoadForest::Graph
111
108
  return value
112
109
  end
113
110
 
111
+ def <<(data)
112
+ case data
113
+ when Array
114
+ data = normalize_statement(*data)
115
+ end
116
+ access_manager << data
117
+ end
118
+
114
119
  def delete(property, extra=nil)
115
120
  access_manager.delete(:subject => subject, :predicate => normalize_property(property, extra))
116
121
  end
@@ -148,11 +153,25 @@ module RoadForest::Graph
148
153
  node
149
154
  end
150
155
 
156
+ def empty_list(property, extra=nil)
157
+ unless extra.nil?
158
+ property = normalize_property([property, extra])
159
+ end
160
+ list
161
+ end
162
+
151
163
  def add_list(property, extra=nil)
152
- list = FocusList.new(::RDF::Node.new, access_manager)
153
- access_manager.insert([subject, normalize_property(property, extra), list.subject])
154
- yield list if block_given?
155
- return list
164
+ unless extra.nil?
165
+ property = normalize_property([property, extra])
166
+ end
167
+ if block_given?
168
+ list = add_node(property).as_list
169
+ yield list
170
+ else
171
+ list = FocusList.new
172
+ add(property, list.subject)
173
+ end
174
+ list
156
175
  end
157
176
  #### End of old GraphFocus
158
177
 
@@ -171,8 +190,6 @@ module RoadForest::Graph
171
190
  return nil if value.nil?
172
191
  if RDF::Literal === value
173
192
  value.object
174
- elsif value == RDF.nil
175
- nil
176
193
  else
177
194
  wrap_node(value)
178
195
  end