$:.unshift File.join(File.dirname(__FILE__),'..') require 'sinatra/base' require 'erb' require 'json' require 'fileutils' require 'rhoconnect' # all middlewares Dir[File.join(File.dirname(__FILE__),'middleware','**','*.rb')].each { |mw| require mw } module Rhoconnect class ApiException < Exception attr_accessor :error_code def initialize(error_code,message) super(message) @error_code = error_code end end class Server < Sinatra::Base set :static, true set :stats, false # default secret @secret = '' # Setup route and mimetype for bulk data downloads # TODO: Figure out why "mime :data, 'application/octet-stream'" doesn't work Rack::Mime::MIME_TYPES['.data'] = 'application/octet-stream' include Rhoconnect # Set rhoconnect middleware set :use_middleware, Proc.new { return false if @middleware_configured # Middleware might be configured only once! use Rhoconnect::Middleware::XDomainSessionWrapper use Rack::Cors do |cfg| cfg.allow do |allow| allow.origins(/.*/) allow.resource '/application', :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true allow.resource '/application/*', :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true allow.resource '/api/application', :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true allow.resource '/api/application/*', :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true allow.resource "/app/#{Rhoconnect::API_VERSION}/*", :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true allow.resource "/rc/#{Rhoconnect::API_VERSION}/app/*", :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true end end use Rhoconnect::Middleware::BodyContentTypeParser use Rhoconnect::Middleware::Stats Rhoconnect::Server.set :secret, @secret unless settings.respond_to?(:secret) use Rack::Session::Cookie, :key => 'rhoconnect_session', :expire_after => Rhoconnect.cookie_expire, :secret => Rhoconnect::Server.secret use Rhoconnect::Middleware::CurrentRequest use Rhoconnect::Middleware::CurrentApp use Rhoconnect::Middleware::CurrentUser use Rhoconnect::Middleware::AdminUser use Rhoconnect::Middleware::LoginRequired @middleware_configured ||= true } helpers do def request_action request.env['PATH_INFO'].split('/').last end def check_login begin yield rescue LoginException => le throw :halt, [401, le.message] rescue Exception => e throw :halt, [500, e.message] end end def do_login check_login do if login status(200) else raise LoginException.new("Unable to authenticate '#{params[:login]}'") end end end def do_ans_login check_login do ans_login ? status(204) : status(401) end end def login if params[:login].nil? or params[:login].empty? return false end if params[:login] == 'rhoadmin' user = User.authenticate(params[:login], params[:password]) elsif current_app and current_app.can_authenticate? and params[:login] user = current_app.authenticate(params[:login], params[:password], session) end if user session[:login] = user.login session[:app_name] = APP_NAME else false end end def ans_login if current_app and current_app.can_ans_authenticate? auth = Rack::Auth::Basic::Request.new(request.env) current_app.ans_authenticate( auth.credentials.first, auth.credentials.last ) if auth.provided? and auth.basic? and auth.credentials else false end end def logout session[:login] = nil end def current_user @env[Rhoconnect::CURRENT_USER] end def current_app @env[Rhoconnect::CURRENT_APP] end def current_client if @client.nil? client_id = @env[Rhoconnect::CLIENT_ID_HEADER] client_id = params[:client_id] unless client_id if client_id @client = Client.load(client_id.to_s, params[:source_name] ? {:source_name => current_source.name} : {:source_name => '*'}) if @client and current_user and @client.user_id != current_user.login @client.switch_user(current_user.login) end end end @client end def current_source return @source if @source user = current_user if params[:source_name] and user @source = Source.load(params[:source_name], {:user_id => user.login,:app_id => APP_NAME}) # if source does not exist create one for dynamic adapter unless @source sconfig = Rhoconnect.source_config(params[:source_name]) @source = Source.create(sconfig.merge!({:name => params[:source_name]}),{:user_id => user.login, :app_id => APP_NAME}) current_app.sources << @source.name end @source else log "ERROR: Can't load source, no source_name provided.\n" nil end end def current_client_sync ClientSync.new(current_source,current_client,params[:p_size]) end def catch_all begin yield rescue ApiException => ae throw :halt, [ae.error_code, ae.message] rescue Exception => e log e.message + e.backtrace.join("\n") throw :halt, [500, e.message] end end def mark_deprecated_call_and_reroute(name, namespace, *params) namespace_val = namespace.nil? ? "" : "#{namespace}" http_method = request.get? ? "GET" : "POST" warning_message = "Use of the #{http_method} #{request.path} is deprecated. Use #{http_method} /api/#{namespace_val}/#{name} instead." response.headers['Warning'] = warning_message Rhoconnect.log warning_message if namespace != nil call env.merge('PATH_INFO' => "/api/#{namespace}/#{name}") else yield *params end end def mark_deprecated_call_and_reroute_api4(verb, new_route, old_verb, old_route, client_call, &block) warning_message = "Use of the #{old_verb.to_s.upcase} #{old_route} is deprecated. Use Rhoconnect API #{Rhoconnect::API_VERSION} instead." response.headers['Warning'] = warning_message Rhoconnect.log warning_message execute_api_call(client_call, &block) end def execute_api_call(client_call = false) catch_all do res = yield params, current_user, self if params.has_key? :warning Rhoconnect.log params[:warning] response.headers['Warning'] = params[:warning] end res end end end private def self._use_async_framework return false if settings.respond_to?(:use_async_model) and settings.use_async_model == false return false if @dispatch_framework_initialized @dispatch_framework_initialized ||=true if RUBY_VERSION =~ /1.9/ and not defined?(JRUBY_VERSION) begin require 'rhoconnect/async' register Rhoconnect::Synchrony helpers Rhoconnect::AsyncHelpers rescue LoadError => e # if it fails here - Async can not be used settings.use_async_model = false warning_for_async_gems = <<_INSTALL_ASYNC_GEMS ***** WARNING ***** Rhoconnect has detected that Ruby 1.9.x is used and tried to initialize Async Framework, but failed: #{e.inspect} Make sure to include the following dependencies into your application's Gemfile: platforms :ruby_19, :mingw_19 do gem 'rack-fiber_pool' gem 'async-rack' end After that, run 'bundle install' to install the necassary dependency gems. ***** WARNING ***** _INSTALL_ASYNC_GEMS puts warning_for_async_gems end else settings.use_async_model = false end end def self.new # by default, enable this feature if not settings.respond_to?(:use_async_model) or settings.use_async_model != false set :use_async_model, true end # this must be called first - because # it redefines some of the middleware Rhoconnect::Server._use_async_framework if settings.respond_to?(:stats) and settings.send(:stats) == true Rhoconnect.stats = true else Rhoconnect::Server.disable :stats Rhoconnect.stats = false end settings.use_middleware super end def initialize # Whine about default session secret check_default_secret!(Rhoconnect::Server.secret) super end Rhoconnect.log "Rhoconnect Server v#{Rhoconnect::VERSION} started..." before do cache_control :no_cache headers({'pragma'=>'no-cache'}) end get '/' do redirect "/console/" end def self.api(name, namespace = nil, verb = :post, &block) # this method is deprecated - so we should warn the users # that it will be removed in 4.0 warning_for_server_api = <<_DEPRECATE_SERVER_API ***** WARNING ***** Server.api method is deprecated and will be removed in Rhoconnect 4.0 Please change your implementation to Server.api4 _DEPRECATE_SERVER_API puts warning_for_server_api old_api_prefix = (namespace == :application) ? :application : :api client_call = (namespace == :application) ? true : false send verb, "/#{old_api_prefix}/#{name}" do mark_deprecated_call_and_reroute(name, namespace, params, &block) end send verb, "/api/#{namespace}/#{name}" do execute_api_call(client_call, &block) end end def self.api4(method_name, verb, route, admin = true, deprecated_route = nil, &block) unless deprecated_route.nil? deprecated_urls = deprecated_route[:url].is_a?(String) ? [deprecated_route[:url]] : deprecated_route[:url] deprecated_urls.each do |deprecated_url| send deprecated_route[:verb], deprecated_url do mark_deprecated_call_and_reroute_api4(verb, route, deprecated_route[:verb], deprecated_url, !admin, &block) end end end send verb, route do execute_api_call(!admin, &block) end end end end include Rhoconnect Dir[File.join(File.dirname(__FILE__),'api','**','*.rb')].each { |api| load api }