openplacos 0.0.8 → 0.0.9

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