openplacos 0.0.8 → 0.0.9

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.8
1
+ 0.0.9
data/lib/openplacos.rb CHANGED
@@ -15,6 +15,5 @@
15
15
  #
16
16
 
17
17
  # Please keep as it, each file should be included separetly
18
- require 'openplacos/libplugin'
18
+ require 'openplacos/libcomponent'
19
19
  require 'openplacos/libclient'
20
- require 'openplacos/libdriver'
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  # This file is part of Openplacos.
2
3
  #
3
4
  # Openplacos is free software: you can redistribute it and/or modify
@@ -13,177 +14,413 @@
13
14
  # You should have received a copy of the GNU General Public License
14
15
  # along with Openplacos. If not, see <http://www.gnu.org/licenses/>.
15
16
 
17
+ require 'socket'
18
+ require 'net/http'
19
+ require 'json'
20
+ require 'oauth2'
21
+ require 'yaml'
22
+
23
+ require File.dirname(__FILE__) + "/widget/modules.rb"
24
+
25
+
26
+ module OAuth2
27
+ class AccessToken
28
+ def to_hash()
29
+ token_params = @params
30
+ token_params['access_token'] = @token
31
+ token_params.merge!(@options)
32
+ token_params
33
+ end
34
+
35
+ end
36
+ end
16
37
 
17
- ENV["DBUS_THREADED_ACCESS"] = "1" #activate threaded dbus
18
- require 'dbus-openplacos'
19
38
 
20
39
  module Openplacos
21
- class Client # client for openplacos server
22
- attr_accessor :config, :objects, :service, :sensors, :actuators, :rooms, :reguls, :initial_room
23
-
24
- def initialize
25
- if(ENV['DEBUG_OPOS'] ) ## Stand for debug
26
- @bus = DBus::SessionBus.instance
40
+
41
+ module String
42
+
43
+ # this method extend string module.
44
+ # this module find out a module instance from it's name (=string)
45
+ # ( "Openplacos::Analog" (=string) => Oenplacos::Analog (= module)
46
+ def get_max_const
47
+ array = self.split("::")
48
+ out = Kernel
49
+ array.each { |mod|
50
+ if out.const_defined?(mod)
51
+ out = out.const_get(mod)
52
+ else
53
+ return out
54
+ end
55
+ }
56
+ return out #Should never be here
57
+ end
58
+ end
59
+
60
+ module Connection
61
+
62
+ # register the client (automatic way)
63
+ def register( )
64
+ postData = Net::HTTP.post_form(URI.parse("#{@url}/oauth/apps"),
65
+ { 'name' =>"#{@name}",
66
+ 'redirect_uri'=>@redirect_uri,
67
+ 'format' =>'json'
68
+ }
69
+ )
70
+ if postData.code == "200" # Check the status code
71
+ client_param = JSON.parse( postData.body)
27
72
  else
28
- @bus = DBus::SystemBus.instance
73
+ puts "error code"
74
+ exit 0
29
75
  end
30
- if @bus.service("org.openplacos.server").exists?
31
- @service = @bus.service("org.openplacos.server")
32
- @service.introspect
33
- #discover all objects of server
34
- @initial_room = Room.new(nil, "/")
35
- @objects = get_objects(@service.root, @initial_room)
36
- @rooms = @initial_room.tree
37
-
38
-
39
- #get sensors and actuators
40
- @sensors = get_sensors
41
- @actuators = get_actuators
42
- @reguls = get_reguls
43
-
44
- @permissions = Hash.new
76
+
77
+ @client_id = client_param["client_id"]
78
+ @client_secret = client_param["client_secret"]
79
+ end
80
+
81
+ def create_client
82
+ @client = OAuth2::Client.new(@client_id,
83
+ @client_secret,
84
+ {:site => "#{@url}",
85
+ :token_url => '/oauth/authorize'
86
+ }
87
+ )
88
+ end
89
+
90
+ def get_grant_url(type_)
91
+ @client.auth_code.authorize_url(:redirect_uri => @redirect_uri, :scope => @scope.join(" "))
92
+ end
93
+
94
+ # save config with token, client_id and secret into a userspace directory
95
+ # needed to not regrant client every connection
96
+ def save_config
97
+ @token_params[@url] = @token ? @token.to_hash : {}
98
+ @token_params[@url][:client_id] = @client_id
99
+ @token_params[@url][:client_secret] = @client_secret
100
+
101
+ File.open(@file_config, 'w') do |out|
102
+ YAML::dump( @token_params, out )
103
+ end
104
+ end
105
+
106
+ # restore config
107
+ def load_config
108
+ if File.exists?(@file_config)
109
+ @token_params = YAML::load(File.read(@file_config))
110
+ if @token_params[@url]
111
+ @client_id = @token_params[@url][:client_id]
112
+ @client_secret = @token_params[@url][:client_secret]
113
+ end
45
114
  else
46
- puts "Can't find OpenplacOS server"
47
- Process.exit 1
115
+ @token_params = Hash.new
48
116
  end
117
+ end
118
+
119
+ def recreate_token
120
+ @token = OAuth2::AccessToken.from_hash(@client, @token_params[@url])
121
+ end
122
+
123
+ end
124
+
125
+ class Connection_password
126
+ include Connection
127
+ attr_reader :token
128
+ def initialize(url_, name_, scope_, id_, port_, username_, password_)
129
+ @url = url_
130
+ @name = name_
131
+ @scope = scope_
132
+ @id = id_
133
+ @redirect_uri = "http://0.0.0.0:#{port_}"
134
+ @port = port_
135
+ @username = username_
136
+ @password = password_
49
137
 
50
-
51
- end
52
-
53
- def get_objects(nod, father_) #get objects from a node, ignore Debug objects
54
- obj = Hash.new
55
- nod.each_pair{ |key,value|
56
- if not(key=="Debug" or key=="server" or key=="plugins" or key=="Authenticate") #ignore debug objects
57
- if not value.object.nil?
58
- obj[value.object.path] = value.object
59
- father_.push_object(value.object)
60
- else
61
- children = father_.push_child(key)
62
- obj.merge!(get_objects(value, children))
63
- end
64
- end
65
- }
66
- obj
138
+
139
+ dir_config = "#{ENV['HOME']}/.openplacos"
140
+ if !Dir.exists?(dir_config)
141
+ Dir.mkdir(dir_config)
142
+ end
143
+
144
+ @file_config = "#{dir_config}/#{@name}-#{id_}.yaml"
145
+
146
+ load_config
147
+ if @token_params[@url].nil? #create -- first time
148
+ register
149
+ create_client
150
+ save_config
151
+ else # persistant mode
152
+ create_client
153
+ end
154
+ get_token
155
+
67
156
  end
68
157
 
69
- def get_config_from_objects(objects) #contact config methods for all objects
70
- cfg = Hash.new
71
- objects.each_pair{ |key, obj|
72
- if not(obj["org.openplacos.server.config"]==nil)
73
- cfg[key] = obj["org.openplacos.server.config"].getConfig[0]
74
- end
75
- }
76
- cfg
77
- end
158
+ private
78
159
 
79
- def get_sensors
80
- sensors = Hash.new
81
- @objects.each_pair{ |key, value|
82
- if value.has_iface?('org.openplacos.server.measure')
83
- sensors[key] = value['org.openplacos.server.measure']
84
- end
85
- }
86
- sensors
160
+ def get_token
161
+ begin
162
+ @token = @client.password.get_token(@username, @password, {:redirect_uri => @redirect_uri},{:mode=>:header, :header_format=>"OAuth %s", :param_name=>"oauth_token"})
163
+ rescue => e
164
+ puts e
165
+ retry
166
+ end
87
167
  end
88
168
 
89
- def is_regul(sensor)
90
- # get dbus object of sensor
91
- key = get_sensors.index(sensor)
92
- if (key == nil)
93
- return false
169
+ end
170
+
171
+ class Connection_auth_code
172
+ include Connection
173
+ attr_reader :token
174
+
175
+ # Open a connection to openplacos server
176
+ # Please give:
177
+ # * opos url
178
+ # * an application name that identify the client oath2 talking
179
+ # * a scope, typically ["read", "write", "user"]
180
+ # * an optionnal id, to manage several clients
181
+ # * port of openplacos server
182
+ def initialize(url_, name_, scope_, id_, port_)
183
+ @url = url_
184
+ @name = name_
185
+ @scope = scope_
186
+ @redirect_uri = "http://0.0.0.0:#{port_}"
187
+ @port = port_
188
+
189
+ dir_config = "#{ENV['HOME']}/.openplacos"
190
+ if !Dir.exists?(dir_config)
191
+ Dir.mkdir(dir_config)
94
192
  end
95
193
 
96
- # Test interface
97
- if (@objects[key].has_iface?('org.openplacos.server.regul'))
98
- return true
99
- else
100
- return false
194
+ # config saved to avoir re-grant at each connection
195
+ @file_config = "#{dir_config}/#{@name}-#{id_}.yaml"
196
+
197
+ load_config
198
+ if @token_params[@url].nil? #get token -- first time
199
+ register
200
+ create_client
201
+ grant
202
+ get_token
203
+ save_config
204
+ else # persistant mode
205
+ create_client
206
+ recreate_token
101
207
  end
102
208
  end
103
209
 
104
- def get_regul_iface(sensor)
105
- if (is_regul(sensor)==nil)
106
- return nil
210
+ private
211
+ # display a message indicating url for grant
212
+ # this method can be overloaded by client depending it's interface with user
213
+ def grant ()
214
+ go_to_url = get_grant_url("code")
215
+
216
+ puts "***************"
217
+ puts "Please open your web browser and got to :"
218
+ puts go_to_url
219
+ puts "***************"
220
+
221
+ @auth_code = get_auth_code
222
+ end
223
+
224
+ def get_auth_code()
225
+ # listen to get the auth code
226
+ server = TCPServer.new(@port)
227
+ re=/code=(.*)&scope/
228
+ authcode = nil
229
+ while (session = server.accept)
230
+ request = session.gets
231
+ authcode = re.match(request)
232
+ if !authcode.nil?
233
+ session.print "HTTP/1.1 200/OK\rContent-type: text/html\r\n\r\n"
234
+ session.puts "<h1>Auth successfull</h1>"
235
+ session.close
236
+ break
237
+ end
238
+ end
239
+ authcode[1]
240
+ end
241
+
242
+ def get_token()
243
+ begin
244
+ @token = @client.auth_code.get_token(@auth_code, {:redirect_uri => @redirect_uri},{:mode=>:header, :header_format=>"OAuth %s", :param_name=>"oauth_token"})
245
+ rescue => e
246
+ puts e.description
247
+ Process.exit 42
107
248
  end
108
- key = get_sensors.index(sensor)
109
- return @objects[key]['org.openplacos.server.regul']
110
249
  end
250
+
251
+ end
111
252
 
112
- def get_actuators
113
- actuators = Hash.new
114
- @objects.each_pair{ |key, value|
115
- if value.has_iface?('org.openplacos.server.actuator')
116
- actuators[key] = value['org.openplacos.server.actuator']
253
+
254
+ class Connection_from_token
255
+ attr_accessor :token
256
+ def initialize(tok_)
257
+ @token = tok_
258
+ end
259
+ end
260
+
261
+ class Client
262
+
263
+ attr_accessor :config, :objects, :service, :sensors, :actuators, :rooms, :reguls, :initial_room
264
+
265
+ # Initialize a connection to server with OAuth2 in a automatic way
266
+ # Please provide url server, application name, permission needed for application
267
+ # Set connection_type to auth_code to use with oauth2 flow
268
+ # You can access to proxyfied objects with .objects attribute
269
+ # Please give:
270
+ # * opos url
271
+ # * an application name that identify the client oath2 talking
272
+ # * a scope, typically ["read", "write", "user"]
273
+ # * a connection_type, set it to "auth_code" to use oauth2 with classic flow (recommanded)
274
+ # or with "password" to use with password flow. Set to "inception" to pass a connection
275
+ # object through opt{:connection}
276
+ # * an optionnal id, to manage several clients
277
+ # * an optionnal option hash, in which you can specify openplacos port { :port => 5454 }
278
+ # * You can also pass a token object through opt[:token] that is an oauth2 object.
279
+ def initialize(url_, name_, scope_, connection_type_, id_ = "0", opt_={})
280
+
281
+ @objects = Hash.new
282
+ @connection_type = connection_type_
283
+ if opt_[:token].nil?
284
+ case @connection_type
285
+ when "auth_code" then
286
+ @connection = Connection_auth_code.new(url_, name_, scope_, id_, opt_[:port] || 2000)
287
+ when "password" then
288
+ @connection = Connection_password.new(url_, name_, scope_, id_, opt_[:port] || 2000, opt_[:username], opt_[:password])
289
+ else
290
+ raise "unknow grant type"
117
291
  end
292
+ else
293
+ @connection = Connection_from_token.new(opt_[:token])
294
+ end
295
+ introspect
296
+ extend_objects
297
+ end
298
+
299
+ # Intropect the distant server
300
+ def introspect
301
+ @introspect = JSON.parse( @connection.token.get('/ressources').body)
302
+ @introspect.each { |obj|
303
+ @objects[obj["name"]] = ProxyObject.new(@connection, obj)
118
304
  }
119
- actuators
120
305
  end
121
306
 
122
- def get_reguls
123
- reguls = Hash.new
124
- @objects.each_pair{ |key, value|
125
- if value.has_iface?('org.openplacos.server.regul')
126
- reguls[key] = value['org.openplacos.server.regul']
307
+ def get_iface_type(obj, det_)
308
+ a = Array.new
309
+ obj.interfaces.each { |iface|
310
+ if(iface.include?(det_))
311
+ a << iface
127
312
  end
128
313
  }
129
- reguls
314
+ a
130
315
  end
131
316
 
132
- def auth(login_,password_)
133
- authobj = @service.object("/Authenticate")["org.openplacos.authenticate"]
134
- ack,perm = authobj.authenticate(login_,password_)
135
- if ack==true
136
- if @permissions[login_].nil?
137
- @permissions[login_] = perm
317
+ def extend_objects
318
+ @objects.each_pair{ |key, obj|
319
+ if (key != "/informations")
320
+ obj.interfaces.each { |iface|
321
+ extend_iface(iface, obj[iface])
322
+ }
138
323
  end
139
- end
140
- return ack
324
+ }
141
325
  end
142
326
 
143
- def readable?(path_,login_)
144
- return true if @permissions[login_]["read"].include?(path_)
145
- #check if one object is readable in the room
146
- @permissions[login_]["read"].each { |path|
147
- return true if path.include?(path_)
327
+ # Extend an object to a ruby module according to iface_name_
328
+ # if iface_name_ is "org.openplacos.analog.order"
329
+ # => object will inherit Openplacos::Analog::Order
330
+ def extend_iface(iface_name_,obj_ )
331
+ mod = "Openplacos::"+ construct_module_name(iface_name_)
332
+ mod.extend(Openplacos::String)
333
+ obj_.extend(mod.get_max_const)
334
+ end
335
+
336
+ # transform iface_name to a module name that obj will inherit
337
+ def construct_module_name(iface_name_)
338
+ iface_heritage = iface_name_.sub(/org.openplacos./, '').split('.')
339
+ iface_heritage.each { |s|
340
+ s.capitalize!
148
341
  }
149
- return false #else
342
+ iface_heritage.join('::')
150
343
  end
151
344
 
152
- def writeable?(path_,login_)
153
- return @permissions[login_]["write"].include?(path_)
345
+ # return the user name
346
+ def me
347
+ JSON.parse(@connection.token.get("/me").body)["username"]
154
348
  end
155
-
349
+
156
350
  end
157
351
 
158
- class Room
159
- attr_accessor :father, :childs, :path, :objects
160
352
 
161
- def initialize(father_, path_)
162
- @father = father_
163
- @path = path_
164
- @childs = Array.new
165
- @objects = Hash.new
166
- end
353
+ class ProxyObject
354
+
355
+ attr_reader :path
356
+
357
+ # Object abstraction of a ressources
358
+ # Contructed from instrospect
359
+ # Has interfaces
360
+ def initialize(connection_, introspect_)
361
+ @path = introspect_["name"]
362
+ @interfaces = Hash.new
363
+ introspect_["interfaces"].each_pair { |name, methods|
364
+ @interfaces[name]= ProxyObjectInterface.new(connection_, self, name, methods)
365
+ }
366
+ end
367
+
368
+ # Returns the interfaces of the object.
369
+ def interfaces
370
+ @interfaces.keys
371
+ end
167
372
 
168
- def push_child (value_)
169
- children = Room.new(self, self.path + value_ + "/")
170
- @childs << children
171
- return children
373
+ # Retrieves an interface of the proxy object (ProxyObjectInterface instance).
374
+ def [](intfname)
375
+ @interfaces[intfname]
172
376
  end
173
377
 
174
- def push_object(obj_)
175
- @objects.store(obj_.path, obj_)
378
+ # Maps the given interface name _intfname_ to the given interface _intf.
379
+ def []=(intfname, intf)
380
+ @interfaces[intfname] = intf
381
+ end
382
+
383
+ def has_iface?(iface_)
384
+ return interfaces.include?(iface_)
176
385
  end
177
-
178
- def tree()
179
- hash = Hash.new
180
- hash.store(@path, self)
181
- @childs.each { |child|
182
- hash.merge!(child.tree)
386
+
387
+ end
388
+
389
+ class ProxyObjectInterface
390
+
391
+ # Interface abstraction
392
+ # contruct from introspect
393
+ def initialize(connection_, proxyobj_, name_, methods_)
394
+ @connection = connection_
395
+ @proxyobject = proxyobj_
396
+ @name = name_
397
+ @methods = Hash.new
398
+ methods_.each { |meth|
399
+ @methods[meth] = define_method(meth)
183
400
  }
184
- return hash
185
401
  end
186
-
402
+
403
+ # Define a proxyfied method from its name
404
+ def define_method(name_)
405
+ if name_=="read"
406
+ methdef = <<-eos
407
+ def read(option_ = {})
408
+ res = JSON.parse(@connection.token.get('/ressources/#{@proxyobject.path}', :params => {'iface'=> '#{@name}', 'options' => (option_).to_json}).body)
409
+ res["value"]
410
+ end
411
+ eos
412
+ end
413
+ if name_=="write"
414
+ methdef = <<-eos
415
+ def write(value_,option_ = {})
416
+ res = JSON.parse(@connection.token.post('/ressources/#{@proxyobject.path}', :params => {'iface'=> '#{@name}', 'value' => [value_].to_json, 'options' => (option_).to_json}).body)
417
+ res["status"]
418
+ end
419
+ eos
420
+ end
421
+
422
+ instance_eval( methdef )
423
+ end
187
424
  end
188
-
425
+
189
426
  end