riddl 0.99.218 → 0.99.219

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+ require 'securerandom'
4
+ require 'json'
5
+
6
+ module Riddl
7
+ module Utils
8
+ module OAuth2
9
+
10
+ module Helper
11
+ class Tokens #{{{
12
+ def initialize(tfile)
13
+ @tfile = tfile
14
+ @changed = changed
15
+ read
16
+ end
17
+
18
+ def [](name)
19
+ read if changed != @changed
20
+ @tokens[name]
21
+ end
22
+
23
+ def method_missing(name,*opts)
24
+ @tokens.send(name,*opts)
25
+ end
26
+
27
+ def []=(name,value)
28
+ @tokens[name] = value
29
+ write
30
+ nil
31
+ end
32
+
33
+ def changed
34
+ if File.exists?(@tfile)
35
+ File.stat(@tfile).mtime
36
+ else
37
+ @tokens = {}
38
+ write
39
+ end
40
+ end
41
+ private :changed
42
+
43
+ def write
44
+ EM.defer {
45
+ File.write(@tfile, JSON::pretty_generate(@tokens)) rescue {}
46
+ }
47
+ @changed = changed
48
+ end
49
+ private :write
50
+
51
+ def read
52
+ @tokens = JSON::parse(File.read(@tfile)) rescue {}
53
+ end
54
+ private :read
55
+
56
+ def delete(token)
57
+ deleted = @tokens.delete(token)
58
+ write
59
+ deleted
60
+ end
61
+
62
+ def delete_by_value(user_id)
63
+ deleted = @tokens.delete_if { |_, v| v == user_id }
64
+ write
65
+ deleted
66
+ end
67
+ end #}}}
68
+
69
+ def self::header #{{{
70
+ {
71
+ :alg => 'HS256',
72
+ :typ => 'JWT'
73
+ }.to_json
74
+ end #}}}
75
+
76
+ def self::nonce
77
+ SecureRandom::hex(32)
78
+ end
79
+
80
+ def self::sign(secret, what) #{{{
81
+ Base64::urlsafe_encode64 OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, what)
82
+ end #}}}
83
+
84
+ def self::generate_access_token(client_id, secret, dur=3600)# {{{
85
+ h = Base64::urlsafe_encode64 header
86
+ p = Base64::urlsafe_encode64( {
87
+ :iss => client_id,
88
+ :sub => nonce,
89
+ :aud => client_id,
90
+ :exp => Time.now.to_i + dur
91
+ }.to_json)
92
+ s = sign(secret, "#{h}.#{p}")
93
+ "#{h}.#{p}.#{s}"
94
+ end# }}}
95
+ def self::generate_refresh_token(client_id, secret, dur=7776000) # {{{
96
+ token = Base64::urlsafe_encode64({
97
+ :iss => client_id,
98
+ :sub => nonce,
99
+ :exp => Time.now.to_i + dur
100
+ }.to_json)
101
+ "#{token}.#{sign(secret,token)}"
102
+ end# }}}
103
+ def self::generate_optimistic_token(client_id, secret, adur=3600, rdur=7776000) #{{{
104
+ t = generate_access_token(client_id, secret, adur)
105
+ r = generate_refresh_token(client_id, secret, rdur)
106
+ [t, r]
107
+ end #}}}
108
+
109
+ def self::decrypt_with_shared_secret(data, secret) #{{{
110
+ # extract initialization vector from encrypted data for further shenanigans
111
+ iv, encr = data[0...16], data[16..-1]
112
+
113
+ decipher = OpenSSL::Cipher::Cipher.new 'aes-256-cbc'
114
+ decipher.decrypt
115
+
116
+ decipher.key = Digest::SHA256.hexdigest secret
117
+ decipher.iv = iv
118
+
119
+ decipher.update(encr) + decipher.final rescue nil
120
+ end #}}}
121
+ def self::encrypt_with_shared_secret(data, secret) #{{{
122
+ cipher = OpenSSL::Cipher::Cipher.new 'aes-256-cbc'
123
+ cipher.encrypt
124
+
125
+ key = Digest::SHA256.hexdigest secret
126
+ iv = cipher.random_iv
127
+ cipher.key = key
128
+ cipher.iv = iv
129
+
130
+ Base64::urlsafe_encode64(iv + cipher.update(data) + cipher.final) rescue nil
131
+ end #}}}
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,167 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/oauth2-helper')
2
+
3
+ module Riddl
4
+ module Utils
5
+ module OAuth2
6
+
7
+ module UnivieBearer
8
+ def self::implementation(client_id, client_secret, access_tokens)
9
+ Proc.new do
10
+ run CheckAuth, client_id, client_secret, access_tokens if get
11
+ end
12
+ end
13
+
14
+ class CheckAuth < Riddl::Implementation
15
+ def response
16
+ client_id = @a[0]
17
+ client_secret = @a[1]
18
+ access_tokens = @a[2]
19
+ if @h['AUTHORIZATION']
20
+ token = @h['AUTHORIZATION'].sub(/^Bearer /, '')
21
+
22
+ data, _, signature = token.rpartition '.'
23
+ expected_sign = Riddl::Utils::OAuth2::Helper::sign(client_id + ':' + client_secret, data)
24
+
25
+ if !access_tokens.key? token
26
+ @status = 403
27
+ return Riddl::Parameter::Complex.new('data', 'application/json', {
28
+ :error => 'Unknown token'
29
+ }.to_json)
30
+ elsif signature != expected_sign
31
+ @status = 403
32
+ return Riddl::Parameter::Complex.new('data', 'application/json', {
33
+ :error => 'Invalid token, you bad boy'
34
+ }.to_json)
35
+ end
36
+
37
+ header_claims, payload_claims = data.split('.').map { |v| Base64::urlsafe_decode64 v }
38
+ payload_claims = JSON::parse payload_claims
39
+
40
+ if header_claims != Riddl::Utils::OAuth2::Helper::header
41
+ @status = 401
42
+ return Riddl::Parameter::Complex.new('data', 'application/json', {
43
+ :error => 'Invalid header claims'
44
+ }.to_json)
45
+ elsif payload_claims['exp'] <= Time.now.to_i
46
+ @status = 403
47
+ return Riddl::Parameter::Complex.new('data', 'application/json', {
48
+ :error => 'Expired token'
49
+ }.to_json)
50
+ elsif !payload_claims['aud'].split(',').map(&:strip).include? client_id
51
+ # XXX: ein token für mehrere clients gültig? lookup?
52
+ @status = 403
53
+ return Riddl::Parameter::Complex.new('data', 'application/json', {
54
+ :error => 'Token is not valid for this application'
55
+ }.to_json)
56
+ end
57
+
58
+ @headers << Riddl::Header.new('AUTHORIZATION_BEARER', access_tokens[token])
59
+ end
60
+
61
+ @p
62
+ end
63
+ end
64
+ end
65
+
66
+ module UnivieApp
67
+ def self::implementation(client_id, client_secret, access_tokens, refresh_tokens)
68
+ Proc.new do
69
+ on resource 'verify' do
70
+ run VerifyIdentity, access_tokens, refresh_tokens, client_id, client_secret if post 'verify_in'
71
+ end
72
+ on resource 'token' do
73
+ run RefreshToken, access_tokens, refresh_tokens, client_id, client_secret if post 'refresh_token_in'
74
+ end
75
+ on resource 'revoke' do
76
+ run RevokeTokenFlow, access_tokens, refresh_tokens if get 'revoke_token_in'
77
+ run RevokeUserFlow, access_tokens, refresh_tokens if get 'revoke_user_in'
78
+ end
79
+ end
80
+ end
81
+
82
+ class VerifyIdentity < Riddl::Implementation
83
+ def response
84
+ code = Base64::urlsafe_decode64 @p[0].value
85
+ access_tokens = @a[0]
86
+ refresh_tokens = @a[1]
87
+ client_id = @a[2]
88
+ client_secret = @a[3]
89
+
90
+ client_pass = "#{client_id}:#{client_secret}"
91
+ user_id, decrypted = Riddl::Utils::OAuth2::Helper::decrypt_with_shared_secret(code, client_pass).split(':', 2)
92
+ token, refresh_token = Riddl::Utils::OAuth2::Helper::generate_optimistic_token(client_id, client_pass)
93
+ access_tokens[token] = user_id
94
+ refresh_tokens[refresh_token] = token
95
+
96
+ json_response = {
97
+ :access_token => token,
98
+ :refresh_token => refresh_token,
99
+ :code => Base64.urlsafe_encode64(decrypted)
100
+ }.to_json
101
+
102
+ Riddl::Parameter::Complex.new('data', 'application/json', json_response)
103
+ end
104
+ end
105
+
106
+ class RevokeTokenFlow < Riddl::Implementation
107
+ def response
108
+ token = @p[0].value
109
+ access_tokens = @a[0]
110
+ refresh_tokens = @a[1]
111
+
112
+ access_tokens.delete(token)
113
+ refresh_tokens.delete_by_value(token)
114
+ end
115
+ end
116
+
117
+ class RevokeUserFlow < Riddl::Implementation
118
+ def response
119
+ user_id = @p[0].value
120
+ access_tokens = @a[0]
121
+ refresh_tokens = @a[1]
122
+
123
+ token = access_tokens.delete_by_value user_id
124
+ refresh_tokens.delete_by_value token
125
+ end
126
+ end
127
+
128
+ class RefreshToken < Riddl::Implementation
129
+ def response
130
+ refresh_token = @p[1].value
131
+ access_tokens = @a[0]
132
+ refresh_tokens = @a[1]
133
+ client_id = @a[2]
134
+ client_secret = @a[3]
135
+
136
+ token, _ = refresh_token.split '.'
137
+ token_data = JSON::parse(Base64::urlsafe_decode64 token)
138
+
139
+ if token_data['iss'] != client_id
140
+ @status = 401
141
+ return Riddl::Parameter::Complex.new('data', 'application/json', {
142
+ :error => 'Token must be refreshed by issuer.'
143
+ }.to_json)
144
+ elsif refresh_tokens[refresh_token].nil? || token_data['exp'] <= Time.now.to_i
145
+ @status = 403
146
+ puts "i dont know #{refresh_token}", "#{refresh_tokens[refresh_token]}"
147
+ return Riddl::Parameter::Complex.new('data', 'application/json', {
148
+ :error => 'Invalid refresh token.'
149
+ }.to_json)
150
+ end
151
+
152
+ old_token = refresh_tokens[refresh_token]
153
+ user = access_tokens.delete old_token
154
+
155
+ token = Riddl::Utils::OAuth2::Helper::generate_access_token(client_id, client_id + ':' + client_secret)
156
+
157
+ refresh_tokens[refresh_token] = token
158
+ access_tokens[token] = user
159
+
160
+ Riddl::Parameter::Complex.new('data', 'application/json', { :token => token }.to_json)
161
+ end
162
+ end
163
+ end
164
+
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,479 @@
1
+ module Riddl
2
+ module Utils
3
+ module Properties
4
+
5
+ VERSION_MAJOR = 1
6
+ VERSION_MINOR = 0
7
+ PROPERTIES_SCHEMA_XSL_RNG = "#{File.dirname(__FILE__)}/../ns/common-patterns/properties/#{VERSION_MAJOR}.#{VERSION_MINOR}/properties.schema.xsl"
8
+
9
+ def self::implementation(backend,handler=nil,details=:production)
10
+ unless handler.nil? || (handler.is_a? Riddl::Utils::Properties::HandlerBase)
11
+ raise "handler not a subclass of HandlerBase"
12
+ end
13
+ Proc.new do
14
+ run Riddl::Utils::Properties::All, backend, handler if get '*'
15
+ run Riddl::Utils::Properties::Query, backend, handler if get 'query'
16
+ on resource 'schema' do
17
+ run Riddl::Utils::Properties::Schema, backend if get
18
+ on resource 'rng' do
19
+ run Riddl::Utils::Properties::RngSchema, backend if get
20
+ end
21
+ end
22
+ on resource 'values' do
23
+ run Riddl::Utils::Properties::Properties, backend, handler if get
24
+ run Riddl::Utils::Properties::AddProperty, backend, handler if post 'property'
25
+ run Riddl::Utils::Properties::AddProperties, backend, handler if put 'properties'
26
+ on resource do
27
+ run Riddl::Utils::Properties::GetContent, backend, handler if get
28
+ run Riddl::Utils::Properties::DelContent, backend, handler if delete
29
+ run Riddl::Utils::Properties::AddContent, backend, handler if post 'addcontent'
30
+ run Riddl::Utils::Properties::UpdContent, backend, handler if put 'updcontent'
31
+ on resource do
32
+ run Riddl::Utils::Properties::GetContent, backend, handler if get
33
+ run Riddl::Utils::Properties::DelContent, backend, handler if delete
34
+ run Riddl::Utils::Properties::UpdContent, backend, handler if put 'updcontent'
35
+ on resource do
36
+ run Riddl::Utils::Properties::GetContent, backend, handler if get
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # Overloadable and Backends
45
+ class HandlerBase #{{{
46
+ def initialize(data)
47
+ @data = data
48
+ @property = nil
49
+ end
50
+ def property(p)
51
+ @property = p
52
+ self
53
+ end
54
+ def create; end
55
+ def read; end
56
+ def update; end
57
+ def delete; end
58
+ end #}}}
59
+
60
+ class Backend #{{{
61
+ attr_reader :schema, :data, :rng
62
+
63
+ def initialize(schema,target,init=nil)
64
+ @target = target.gsub(/^\/+/,'/')
65
+ @schemas = {}
66
+ @rngs = {}
67
+
68
+ if schema.is_a? Hash
69
+ schema.each { |k,v| add_schema k, v }
70
+ elsif schema.is_a? String
71
+ add_schema 'default', schema
72
+ end
73
+ raise "no schemas provided" if @schemas.length == 0
74
+ @schema = @schemas.first[1]
75
+ @rng = @rngs.first[1]
76
+
77
+ FileUtils::mkdir_p(File::dirname(@target)) unless File.exists?(@target)
78
+ FileUtils::cp init, @target if init and not File.exists?(@target)
79
+
80
+ raise "properties file not found" unless File.exists?(@target)
81
+ @data = XML::Smart.open_unprotected(@target)
82
+ @data.register_namespace 'p', 'http://riddl.org/ns/common-patterns/properties/1.0'
83
+ @mutex = Mutex.new
84
+ end
85
+
86
+ def activate_schema(name)
87
+ if @schemas[name]
88
+ @schema = @schemas[name]
89
+ @rng = @rngs[name]
90
+ true
91
+ else
92
+ false
93
+ end
94
+ end
95
+
96
+ def add_schema(key,name)
97
+ raise "schema file not found" unless File.exists?(name)
98
+ @schemas[key] = XML::Smart.open_unprotected(name.gsub(/^\/+/,'/'))
99
+ @schemas[key].register_namespace 'p', 'http://riddl.org/ns/common-patterns/properties/1.0'
100
+ if !File::exists?(Riddl::Utils::Properties::PROPERTIES_SCHEMA_XSL_RNG)
101
+ raise "properties schema transformation file not found"
102
+ end
103
+ @rngs[key] = @schemas[key].transform_with(XML::Smart.open_unprotected(Riddl::Utils::Properties::PROPERTIES_SCHEMA_XSL_RNG))
104
+ end
105
+ private :add_schema
106
+
107
+ def modifiable?(property)
108
+ @schema.find("boolean(/p:properties/p:#{property}[@modifiable='true'])") || schema.find("boolean(/p:properties/p:optional/p:#{property}[@modifiable='true'])")
109
+ end
110
+ def valid_state?(property,current,new)
111
+ @schema.find("boolean(/p:properties/p:#{property}/p:#{current}/p:#{new}[@putable='true'])") || schema.find("boolean(/p:properties/p:optional/p:#{property}/p:#{current}/p:#{new}[@putable='true'])")
112
+ end
113
+ def is_state?(property)
114
+ @schema.find("boolean(/p:properties/p:#{property}[@type='state'])") || schema.find("boolean(/p:properties/p:optional/p:#{property}[@type='state'])")
115
+ end
116
+ def init_state?(property,new)
117
+ @schema.find("boolean(/p:properties/p:#{property}/p:#{new}[position()=1])") || schema.find("boolean(/p:properties/p:optional/p:#{property}/p:#{new}[position()=1])")
118
+ end
119
+ def property_type(property)
120
+ exis = @schema.find("/p:properties/*[name()='#{property}']|/p:properties/p:optional/*[name()='#{property}']")
121
+ exis.any? ? exis.first.attributes['type'].to_sym : nil
122
+ end
123
+
124
+ def modify(&block)
125
+ tdoc = @data.root.to_doc
126
+ tdoc.register_namespace 'p', 'http://riddl.org/ns/common-patterns/properties/1.0'
127
+ @mutex.synchronize do
128
+ block.call tdoc
129
+ if tdoc.validate_against(@rng){|err| puts err.message }
130
+ block.call @data
131
+ @data.save_as(@target)
132
+ true
133
+ else
134
+ false
135
+ end
136
+ end
137
+ end
138
+ end #}}}
139
+
140
+ # Just reading
141
+ class All < Riddl::Implementation #{{{
142
+ def response
143
+ backend = @a[0]
144
+ handler = @a[1]
145
+ EM.defer{handler.read} unless handler.nil?
146
+ return Riddl::Parameter::Complex.new("document","text/xml",backend.data.to_s)
147
+ end
148
+ end #}}}
149
+
150
+ class Properties < Riddl::Implementation #{{{
151
+ def response
152
+ backend = @a[0]
153
+ handler = @a[1]
154
+ EM.defer{handler.read} unless handler.nil?
155
+
156
+ ret = XML::Smart.string("<properties xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
157
+ backend.schema.find("/p:properties/*[name()!='optional']|/p:properties/p:optional/*").each do |r|
158
+ ret.root.add("property",r.qname.to_s)
159
+ end
160
+ return Riddl::Parameter::Complex.new("keys","text/xml",ret.to_s)
161
+ end
162
+ end #}}}
163
+
164
+ class Query < Riddl::Implementation #{{{
165
+ def response
166
+ backend = @a[0]
167
+ handler = @a[1]
168
+ EM.defer{handler.read} unless handler.nil?
169
+ query = (@p[0].value.to_s.strip.empty? ? '*' : @p[0].value)
170
+
171
+ begin
172
+ e = backend.data.find(query)
173
+ rescue => e
174
+ prop = XML::Smart::string("<not-existing xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>").to_s
175
+ return Riddl::Parameter::Complex.new("value","text/xml",prop.to_s)
176
+ end
177
+ if e.class == XML::Smart::Dom::NodeSet
178
+ if e.any?
179
+ prop = XML::Smart::string("<value xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
180
+ prop.root.add(e)
181
+ else
182
+ prop = XML::Smart::string("<not-existing xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>").to_s
183
+ end
184
+ return Riddl::Parameter::Complex.new("value","text/xml",prop.to_s)
185
+ else
186
+ return Riddl::Parameter::Simple.new("value",e.to_s)
187
+ end
188
+ end
189
+ end #}}}
190
+
191
+ class RngSchema < Riddl::Implementation #{{{
192
+ def response
193
+ backend = @a[0]
194
+ Riddl::Parameter::Complex.new("document-schema","text/xml",backend.rng.to_s)
195
+ end
196
+ end #}}}
197
+
198
+ class Schema < Riddl::Implementation #{{{
199
+ def response
200
+ backend = @a[0]
201
+ return Riddl::Parameter::Complex.new("document-schema","text/xml",backend.schema.to_s)
202
+ end
203
+ end #}}}
204
+
205
+ class GetContent < Riddl::Implementation #{{{
206
+ def response
207
+ backend = @a[0]
208
+ handler = @a[1]
209
+
210
+ EM.defer{handler.property(@r[1]).read} unless handler.nil?
211
+
212
+ if ret = extract_values(backend,@r[1],Riddl::Protocols::Utils::unescape(@r[2..-1].join('/')))
213
+ ret
214
+ else
215
+ @status = 404
216
+ end
217
+ end
218
+
219
+ def extract_values(backend,property,minor=nil)
220
+ case backend.property_type(property)
221
+ when :complex
222
+ res = backend.data.find("/p:properties/*[name()=\"#{property}\"]#{minor == '' ? '' : "/p:#{minor}"}")
223
+ if res.any?
224
+ prop = XML::Smart::string("<value xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
225
+ if res.length == 1
226
+ prop.root.add(res.first.children)
227
+ else
228
+ prop.root.add(res)
229
+ end
230
+ return Riddl::Parameter::Complex.new("value","text/xml",prop.to_s)
231
+ else
232
+ prop = XML::Smart::string("<not-existing xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
233
+ end
234
+ when :simple, :state
235
+ res = backend.data.find("string(/p:properties/*[name()=\"#{property}\"]#{minor})")
236
+ return Riddl::Parameter::Simple.new("value",res.to_s)
237
+ when :arbitrary
238
+ res = backend.data.find("/p:properties/*[name()=\"#{property}\"]")
239
+ if res.any?
240
+ c = res.first.children
241
+ if c.length == 1 && c.first.class == XML::Smart::Dom::Element
242
+ return Riddl::Parameter::Complex.new("content","text/xml",c.first.dump)
243
+ else
244
+ return Riddl::Parameter::Complex.new("content","text/plain",c.first.to_s)
245
+ end
246
+ else
247
+ prop = XML::Smart::string("<not-existing xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
248
+ return Riddl::Parameter::Complex.new("content","text/xml",prop.to_s)
249
+ end
250
+ end
251
+ nil
252
+ end
253
+ private :extract_values
254
+
255
+ end #}}}
256
+
257
+ # Modifiable
258
+ class AddProperty < Riddl::Implementation #{{{
259
+ def response
260
+ backend = @a[0]
261
+ handler = @a[1]
262
+
263
+ property = @p[0].value
264
+ ct = @p[1]
265
+ value = ct.name == 'value' ? ct.value : nil
266
+ content = ct.name == 'content' ? ct.value : nil
267
+
268
+ unless backend.modifiable?(property)
269
+ @status = 500
270
+ return # change properties.schema
271
+ end
272
+
273
+ path = "/p:properties/*[name()=\"#{property}\"]"
274
+ nodes = backend.data.find(path)
275
+ if nodes.any?
276
+ @status = 404
277
+ return # this property does not exist
278
+ end
279
+
280
+ if backend.is_state?(property)
281
+ unless backend.init_state?(property,value)
282
+ @status = 404
283
+ return # not a valid state from here on
284
+ end
285
+ end
286
+
287
+ newstuff = value.nil? ? XML::Smart.string(content).root.children : value
288
+ backend.modify do |doc|
289
+ ele = doc.root.add property
290
+ if value.nil?
291
+ ele.add newstuff
292
+ else
293
+ ele.text = newstuff
294
+ end
295
+ end || begin
296
+ @status = 400
297
+ return # bad request
298
+ end
299
+
300
+ EM.defer{handler.property(property).create} unless handler.nil?
301
+ return
302
+ end
303
+ end #}}}
304
+
305
+ class AddProperties < Riddl::Implementation #{{{
306
+ def response
307
+ backend = @a[0]
308
+ handler = @a[1]
309
+
310
+ 0.upto(@p.length/2-1) do |i|
311
+ property = @p[i*2].value
312
+ ct = @p[i*2+1]
313
+ value = ct.name == 'value' ? ct.value : nil
314
+ content = ct.name == 'content' ? ct.value : nil
315
+
316
+ unless backend.modifiable?(property)
317
+ @status = 500
318
+ return # change properties.schema
319
+ end
320
+
321
+ path = "/p:properties/*[name()=\"#{property}\"]"
322
+ nodes = backend.data.find(path)
323
+ if nodes.empty?
324
+ @status = 404
325
+ return # this property does not exist
326
+ end
327
+
328
+ if backend.is_state?(property)
329
+ unless backend.valid_state?(property,nodes.first.to_s,value)
330
+ @status = 404
331
+ return # not a valid state from here on
332
+ end
333
+ end
334
+
335
+ newstuff = value.nil? ? XML::Smart.string(content).root.children : value
336
+ backend.modify do |doc|
337
+ nodes = doc.find(path)
338
+ nods = nodes.map{|ele| ele.children.delete_all!; ele}
339
+ nods.each do |ele|
340
+ if value.nil?
341
+ ele.add newstuff
342
+ else
343
+ ele.text = newstuff
344
+ end
345
+ end
346
+ end || begin
347
+ @status = 400
348
+ return # bad request
349
+ end
350
+
351
+ end
352
+ EM.defer do
353
+ 0.upto(@p.length/2-1) do |i|
354
+ property = @p[i*2].value
355
+ handler.property(property).create
356
+ end
357
+ end unless handler.nil?
358
+ return
359
+ end
360
+ end #}}}
361
+
362
+ class AddContent < Riddl::Implementation #{{{
363
+ def response
364
+ backend = @a[0]
365
+ handler = @a[1]
366
+
367
+ property = @r[1]
368
+ value = @p.detect{|p| p.name == 'value'}.value
369
+
370
+ unless backend.modifiable?(property)
371
+ @status = 500
372
+ return # change properties.schema
373
+ end
374
+
375
+ path = "/p:properties/p:#{property}"
376
+ node = backend.data.find(path)
377
+ if node.empty?
378
+ @status = 404
379
+ return # this property does not exist
380
+ end
381
+
382
+ newstuff = XML::Smart.string(value)
383
+ backend.modify do |doc|
384
+ node = doc.find(path)
385
+ node.first.add newstuff.root
386
+ end || begin
387
+ @status = 400
388
+ return # bad request
389
+ end
390
+
391
+ EM.defer{handler.property(property).create} unless handler.nil?
392
+ end
393
+ end #}}}
394
+
395
+ class DelContent < Riddl::Implementation #{{{
396
+ def response
397
+ backend = @a[0]
398
+ handler = @a[1]
399
+
400
+ property = @r[1]
401
+ minor = Riddl::Protocols::Utils::unescape(@r[2])
402
+
403
+ unless backend.modifiable?(property)
404
+ @status = 500
405
+ return # change properties.schema
406
+ end
407
+
408
+ path = "/p:properties/*[name()=\"#{property}\"]#{minor.nil? ? '' : "/p:#{minor}"}"
409
+ nodes = backend.data.find(path)
410
+ if nodes.empty?
411
+ @status = 404
412
+ return # this property does not exist
413
+ end
414
+
415
+ backend.modify do |doc|
416
+ doc.find(path).delete_all!
417
+ end || begin
418
+ @status = 400
419
+ return # bad request
420
+ end
421
+
422
+ EM.defer{handler.property(property).delete} unless handler.nil?
423
+ return
424
+ end
425
+ end #}}}
426
+
427
+ class UpdContent < Riddl::Implementation #{{{
428
+ def response
429
+ backend = @a[0]
430
+ handler = @a[1]
431
+
432
+ property = @r[1]
433
+ value = @p.detect{|p| p.name == 'value'}; value = value.nil? ? value : value.value
434
+ content = @p.detect{|p| p.name == 'content'}; content = content.nil? ? content : content.value
435
+ minor = @r[2]
436
+
437
+ unless backend.modifiable?(property)
438
+ @status = 500
439
+ return # change properties.schema
440
+ end
441
+
442
+ path = "/p:properties/*[name()=\"#{property}\"]#{minor.nil? ? '' : "/p:#{minor}"}"
443
+ nodes = backend.data.find(path)
444
+ if nodes.empty?
445
+ @status = 404
446
+ return # this property does not exist
447
+ end
448
+
449
+ if backend.is_state?(property)
450
+ unless backend.valid_state?(property,nodes.first.to_s,value)
451
+ @status = 404
452
+ return # not a valid state from here on
453
+ end
454
+ end
455
+
456
+ newstuff = value.nil? ? XML::Smart.string(content).root.children : value
457
+ backend.modify do |doc|
458
+ nodes = doc.root.find(path)
459
+ nods = nodes.map{|ele| ele.children.delete_all!; ele}
460
+ nods.each do |ele|
461
+ if value.nil?
462
+ ele.add newstuff
463
+ else
464
+ ele.text = newstuff
465
+ end
466
+ end
467
+ end || begin
468
+ @status = 400
469
+ return # bad request
470
+ end
471
+
472
+ EM.defer{handler.property(property).update} unless handler.nil?
473
+ return
474
+ end
475
+ end #}}}
476
+
477
+ end
478
+ end
479
+ end