storing the ip if one user is logged in from different machine
[opensuse:yast-web-client.git] / webclient / lib / yast / service_resource.rb
1 #--
2 # Webyast Webservice framework
3 #
4 # Copyright (C) 2009, 2010 Novell, Inc. 
5 #   This library is free software; you can redistribute it and/or modify
6 # it only under the terms of version 2.1 of the GNU Lesser General Public
7 # License as published by the Free Software Foundation. 
8 #
9 #   This library is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 
12 # details. 
13 #
14 #   You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software 
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 #++
18
19 require 'ostruct'
20
21 module YaST
22
23   # Provides utilities to interact with a YaST webservice
24   #
25   # Session module is used to hold the current session with
26   # a YaST web service
27   #
28   # proxy_for function allows to retrieve a proxy implementing
29   # a certain interface, using introspection to the webservice
30   # to find the resource url.
31   # The returned proxy is also able to handle singleton resources
32   # and to instrospect permissions for the current user.
33   #
34   # Base is a compatibility ActiveResource::Base like class
35   # for fixed url resources
36   #
37   module ServiceResource    
38     #
39     # Creates a proxy for a given interface
40     #
41     # YaST::ServiceResource.proxy_for('org.iface.foo') do |p|
42     #   p.find(:all)
43     # end
44     #
45     # For singleton resources you can use p.find with no arguments
46     # 
47     # The path of the resource is asked to the server resource
48     # registry
49     #
50     # By default, the service used is the one stored in
51     # YaST::ServiceResource::Session which can be overriden
52     #
53     # proxy_for('org.iface.foo', :site => "http://foo") do |p|
54     #  ...
55     # end
56     #
57     # If YaST::ServiceResource::Session does not specify anything
58     # and site not overriden, ActiveResource::Base.site is used.
59     #
60     # ==Permissions==
61     #
62     # YaST::ServiceResource.proxy_for('org.iface.foo') do |p|
63     #   p.permissions
64     # end
65     #
66     # returns the permissions for the current interface
67     # on the server side.
68     #
69     # permissions returns a hash permission-name => granted
70     # example: { :read => true, :write => false }
71     #
72     # Options: :user => 'value' will retrieve the permissions
73     # for the specified user.
74     #
75     # If no user is specified, the current logged user will be
76     # retrieved from YaST::ServiceResource::Session and used
77     # instead.
78     #
79     # Note that if you manually specify an user, you should have
80     # permissions to read those user permissions.
81     #
82     def self.proxy_for(interface_name, opts={})
83       # not used yet
84       # {:site => ActiveResource::Base::, :arg_two => 'two'}.merge!(opts)
85       resource = nil
86       begin
87         resource = self.resource_for_interface(interface_name)
88         raise "null resource, should throw inside resource_for_interface" unless resource
89       rescue Exception => e
90         Rails.logger.warn e
91         return nil
92       end
93
94       proxy = self.class_for_resource(resource, opts)
95       
96       if block_given?
97         yield proxy
98       end
99       return proxy
100     end
101
102     # returns back the rest-service error message if the HTTP error 4** happens
103     def self.error(net_error)
104       begin
105         h = Hash.from_xml(net_error.response.body)["error"]
106       rescue NoMethodError
107         h = { "message" => net_error.response.body }
108       end
109
110       return h.nil? ? h : h["message"]
111     end
112
113     # all dynamic proxies are created under this module
114     module Proxies
115     end
116     
117     # place to hold data related to the
118     # current connected web service
119     module Session
120       # service we are logged in to
121       mattr_accessor :site
122       # login used to access the site
123       mattr_accessor :login
124       # auth_token from session
125       mattr_accessor :auth_token
126     end
127
128     # ActiveResource::Base class is broken with singleton resources
129     # therefore we add some to it
130     #
131     # See: https://rails.lighthouseapp.com/projects/8994/tickets/2608-activeresource-support-for-singleton-resources#ticket-2608-1
132     def self.fix_singleton_proxy(obj)
133       # singularize only if the name is plural
134       obj.collection_name = obj.collection_name.singularize if ( obj.collection_name == obj.collection_name.pluralize)
135       #end
136       
137       def obj.element_path(id, prefix_options = {}, query_options = nil)
138         prefix_options, query_options = split_options(prefix_options) if query_options.nil?
139         # original: "#{prefix(prefix_options)}#{collection_name}/#{id}.#{format.extension}#{query_string(query_options)}"
140         "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
141       end
142
143       # overriding the collection_path to omit the extension and make the collection_name singular
144       def obj.collection_path(prefix_options = {}, query_options = nil)
145         prefix_options, query_options = split_options(prefix_options) if query_options.nil?
146         # original: "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
147         "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
148       end
149     end
150
151     # add the convenience methods to the proxy object
152     # like permissions and resource_uri
153     def self.add_service_proxy_convenience_methods(obj)
154       class << obj
155         
156         def resource_uri
157           URI.join(self.site.to_s, File.join(self.site.path,"#{self.element_name}.xml"))
158         end
159
160         # dynamic implementation of permissions as described
161         # on proxy_for documentation
162         def permissions(opts={})
163           login = opts.fetch(:user, YaST::ServiceResource::Session.login)
164           raise "Can't retrieve permissions. No user specified and not logged in" if not login
165
166           policy_name = nil
167           if self.respond_to? :policy and self.policy
168             policy_name = self.policy
169           end
170
171           if not policy_name
172             raise "object does not implement any interface" if not (self.respond_to?(:interface) and self.interface)
173             policy_name = self.interface
174           end
175
176           perm_resource = OpenStruct.new(:href => '/permissions', :singular => true, :interface => 'org.opensuse.yast.webservice.permissions')
177           proxy = YaST::ServiceResource.class_for_resource(perm_resource)
178           
179           permissions = proxy.find(:all, :params =>
180              { :user_id => login, :filter => policy_name })
181           Rails.logger.debug "returned permissions #{permissions.inspect}"
182           
183           RAILS_DEFAULT_LOGGER.warn "#{proxy.element_name} #{proxy.site}"
184           ret = Hash.new
185           permissions.each do |perm|
186             Rails.logger.debug perm.inspect
187             break if perm.id.nil? # no permissions
188             perm_short_name = perm.id
189             perm_short_name.slice!("#{policy_name}.")
190             # to this point the short name must be something
191             next if perm_short_name.blank?
192             ret[perm_short_name.to_sym] = perm.granted
193           end
194           return ret
195         end
196
197         # Accessor for the interface we implement
198         def interface=(interface_name)
199           @interface = interface_name
200         end
201           
202         def interface
203           defined?(@interface) ? @interface : nil
204         end
205
206         def policy=(policy_name)
207           @policy = policy_name
208         end
209           
210         def policy
211           defined?(@policy) ? @policy : nil
212         end
213
214         def singular=(singular)
215           @singular = singular
216         end
217           
218         def singular?
219           defined?(@singular) ? @singular : false
220         end
221           
222       end
223
224     end
225
226     # Creates a class for a resource based on
227     # the interface name
228     #
229     # This method creates a dynamic class based on
230     # ActiveResource::Base, and preconfigured
231     # with the path where the resource is on
232     # the server based on /resources.xml introspection.
233     #
234     # the dynamic class is defined in YaST::ServiceResource::Proxies
235     # and it is named after the interface:
236     # org.foo.bar -> OrgFooBar
237     # If the class exists, then if it is the same resource path, it
238     # is reused. If it is not the same resource path, then the name
239     # is modified until an unique name is found.
240     #
241     def self.class_for_resource(resource, opts={})
242       # dynamically create an anonymous class for
243       # this resource
244       path = resource.href
245
246       name = File.basename(path)
247       base_path = File.dirname(path)
248
249       # use options site if available, otherwise
250       # the ServiceResource site
251       site = opts.fetch(:site,
252         Session.site.nil? ?
253           ActiveResource::Base.site : Session.site)
254       raise "Invalid site" if site.nil?
255       full_site = URI.join(site.to_s, base_path)
256       
257       rsrc = nil
258       # the module where we store the proxy classes
259       proxy_mod = YaST::ServiceResource::Proxies
260       if not resource.interface.blank?
261         counter = 0
262         while true
263           klass_name = "#{resource.interface.split('.').last.camelize}".to_sym
264           #klass_name = "#{resource.interface.split('.').last.camelize}#{(counter < 1) ? "" : counter}".to_sym
265           
266           #klass_name = "#{resource.interface.tr('.', '_').camelize}#{(counter < 1) ? "" : counter}".to_sym
267           if proxy_mod.const_defined?(klass_name)
268             rsrc = proxy_mod.const_get(klass_name)
269             # if the class has the same path, use it, otherwise, go to next
270             # name
271             if not "#{rsrc.site}" == "#{full_site}"
272               # undefine it, we use send because remove_const is
273               # private, yes black magic
274               proxy_mod.send(:remove_const, klass_name)
275               # set it again
276               rsrc = Class.new(ActiveResource::Base)
277               proxy_mod.const_set(klass_name, rsrc)
278               break
279               #counter = counter + 1
280               # get a new name
281               #next
282             else
283               # otherwise just use the old class
284               break
285             end
286           else
287             # the current name does not exist
288             rsrc = Class.new(ActiveResource::Base)
289             proxy_mod.const_set(klass_name, rsrc)
290             break
291           end
292         end
293       else
294         rsrc = Class.new(ActiveResource::Base)
295       end
296
297       rsrc.site = full_site
298       rsrc.element_name = name.to_s
299       
300       # add convenience method
301       self.add_service_proxy_convenience_methods(rsrc)
302       # if the resource is a singleton add the necessary
303       # black magic
304       self.fix_singleton_proxy(rsrc) if resource.singular?
305
306       # set the interface name of the proxy
307       # that is used when retrieving permissions
308       rsrc.instance_variable_set(:@interface, resource.interface)
309       begin
310         rsrc.instance_variable_set(:@policy, resource.policy)
311       rescue NoMethodError
312         # resource API not updated yet, never mind
313       end
314       rsrc.instance_variable_set(:@singular, resource.singular?)
315       rsrc.password = Session.auth_token if not Session.auth_token.blank?
316       return rsrc
317     end
318
319     # returns the url for a given interface by
320     # querying the remote resource registry
321     # (the resources resource)
322     def self.resource_for_interface(interface_name)
323       res_resource = OpenStruct.new(:href => '/resources', :singular => false)
324
325       proxy = self.class_for_resource(res_resource)
326       resources = proxy.find(:all)
327       resources.each do |resource|
328         return resource if resource.interface == interface_name
329       end
330       return nil
331     end
332   
333     # Obsolete
334     # Just for backward compatibility for resources
335     # using YaST::ServiceResource::Base
336     class Base < ActiveResource::Base
337       # This method is called by Account after a session is
338       # created to use the connected service as default
339       def self.init_service_url(url)
340         self.site = url
341       end
342      
343       # This method is called by Account after a session is
344       # created to use the connected service auth as default
345       def self.set_web_service_auth (auth_token)
346         self.password = auth_token
347         self.user = ""
348       end      
349     end
350
351   end
352 end