lib/databasedotcom-oauth2.rb in databasedotcom-oauth2-0.1.9 vs lib/databasedotcom-oauth2.rb in databasedotcom-oauth2-0.2.0

- old
+ new

@@ -5,87 +5,44 @@ require "hashie" require "gibberish" require "databasedotcom" require "oauth2" -module OAuth2 - class AccessToken - attr_accessor :client - end -end - module Databasedotcom - def self.parse_domain(url = nil) - unless url.nil? - url = "https://" + url if (url =~ /http[s]?:\/\//).nil? - begin - url = Addressable::URI.parse(url) - rescue Addressable::URI::InvalidURIError - url = nil - end - url = url.host unless url.nil? - url.strip! unless url.nil? - end - url = nil if url && url.strip.empty? - url - end - class Client - def self.from_token(token, api_version) - client = nil - unless token.nil? - client = self.new({ - :client_id => token.client.id, - :client_secret => token.client.secret, - :host => Databasedotcom.parse_domain(token.client.site) - }) - m = token["id"].match(/\/id\/([^\/]+)\/([^\/]+)$/) - client.org_id = m[1] rescue nil - client.user_id = m[2] rescue nil - client.version = api_version - client.instance_url = token.client.site - client.oauth_token = token.token - client.refresh_token = token.refresh_token - end - client + + attr_accessor :org_id + attr_accessor :user_id + attr_accessor :endpoint + attr_accessor :last_seen + attr_accessor :logout_flag + + def logout + @logout_flag = true end - def org_id=(val) - @org_id = val - end - - def user_id=(val) - @user_id = val - end - end module OAuth2 - TOKEN_KEY = "databasedotcom.token" CLIENT_KEY = "databasedotcom.client" module Helpers def client - env['databasedotcom.client'] + env[CLIENT_KEY] end - def token - env['databasedotcom.token'] - end - def unauthenticated? client.nil? end def authenticated? !unauthenticated? end def me @me ||= ::Hashie::Mash.new(Databasedotcom::Chatter::User.find(client, "me").raw_hash) - #@me.organization_id end end class WebServerFlow @@ -100,11 +57,11 @@ @immediate = options[:immediate] @prompt = options[:prompt] @scope = options[:scope] @display_override = options[:display_override] || false @immediate_override = options[:immediate_override] || false - @prompt_override = options[:prompt_override] || false + @prompt_override = options[:prompt_override] || false @scope_override = options[:scope_override] || false @api_version = options[:api_version] || "25.0" @debugging = options[:debugging] || false end @@ -134,22 +91,24 @@ def call!(env) @env = env begin return authorize_call if on_authorize_path? return callback_call if on_callback_path? - materialize_token_and_client_from_session_if_present rescue Exception => e self.class._log_exception(e) if @on_failure.nil? new_path = Addressable::URI.parse(@path_prefix + "/failure") new_path.query_values={:message => e.message, :state => request.params['state']} return [302, {'Location' => new_path.to_s, 'Content-Type'=> 'text/html'}, []] else return @on_failure.call(env,e) end end - @app.call(env) + @env[CLIENT_KEY] = retrieve_client_from_session + status, headers, body = @app.call(env) + save_client_to_session(@env[CLIENT_KEY]) + [status, headers, body] end private def on_authorize_path? @@ -168,11 +127,11 @@ request.params["state"] ||= "/" state = Addressable::URI.parse(request.params["state"]) state.query_values={} unless state.query_values state.query_values= state.query_values.merge({:endpoint => endpoint}) - puts "endpoint: #{endpoint}\nmydomain: #{mydomain}\nstate: #{state.to_str}" if @debugging + puts "(1) endpoint: #{endpoint}\n(2) mydomain: #{mydomain}\n(3) state: #{state.to_str}" if @debugging #build params hash to be passed to ouath2 authorize redirect url auth_params = { :redirect_uri => "#{full_host}#{@path_prefix}/callback", :state => state.to_str @@ -184,20 +143,23 @@ #overrides overrides = {} overrides[:display] = request.params["display"] unless !@display_override || request.params["display"].nil? overrides[:immediate] = request.params["immediate"] unless !@immediate_override || request.params["immediate"].nil? - overrides[:prompt] = request.params["prompt"] unless !@prompt_override || request.params["prompt"].nil? + if @prompt_override + prompt = (self.class.param_repeated(request.url, :prompt) || []).join(" ") + overrides[:prompt] = prompt unless prompt.nil? || prompt.strip.empty? + end if @scope_override scope = (self.class.param_repeated(request.url, :scope) || []).join(" ") overrides[:scope] = scope unless scope.nil? || scope.strip.empty? end auth_params.merge!(overrides) #do redirect redirect_url = client(mydomain || endpoint, keys[:key], keys[:secret]).auth_code.authorize_url(auth_params) - puts "redirecting to #{redirect_url}..." if @debugging + puts "(4) redirecting to #{redirect_url}..." if @debugging redirect redirect_url end def on_callback_path? on_path?(@path_prefix + "/callback") @@ -218,102 +180,112 @@ state = Addressable::URI.parse(request.params["state"]) state.query_values= {} if state.query_values.nil? state_params = state.query_values.dup endpoint = state_params.delete("endpoint") keys = @endpoints[endpoint] - puts "endpoint #{endpoint}" - puts "keys #{keys}" + puts "(1) endpoint #{endpoint}" if @debugging + puts "(2) keys #{keys}" if @debugging state.query_values= state_params state = state.to_s state.sub!(/\?$/,"") unless state.nil? - puts "endpoint: #{endpoint}\nstate: #{state.to_str}\nretrieving token" if @debugging + puts "(3) endpoint: #{endpoint}\nstate: #{state.to_str}\nretrieving token" if @debugging #do callout to retrieve token access_token = client(endpoint, keys[:key], keys[:secret]).auth_code.get_token(code, :redirect_uri => "#{full_host}#{@path_prefix}/callback") - puts "access_token immediatly post get token call #{access_token.inspect}" if @debugging - access_token.options[:mode] = :query - access_token.options[:param_name] = :oauth_token - access_token.options[:endpoint] = endpoint - access_token.client = nil - puts "access_token pre marshal-encrypt-cookiewrite #{access_token.inspect}" if @debugging + puts "(4) access_token immediatly post get token call #{access_token.inspect}" if @debugging - #populate session with serialized, encrypted token - #will be used later to materialize actual token and databasedotcom client handle - set_session_token(encrypt(access_token)) - puts "session_token \n#{session_token}" if @debugging + client = self.class.client_from_oauth_token(access_token) + client.endpoint = endpoint + puts "(5) client from token: #{client.inspect}" if @debugging + save_client_to_session(client) + puts "(6) session_client \n#{session_client}" if @debugging redirect state.to_str end - def materialize_token_and_client_from_session_if_present - puts "==========================\nmaterialize intercept\n==========================\n" if @debugging - access_token = nil - puts "session_token \n#{session_token}" if @debugging + def save_client_to_session(client) + puts "==========================\nsave_client_to_session\n==========================\n" if @debugging + puts "(1) client as stored in session \n#{session_client}" if @debugging + puts "(2) client to save: #{client.inspect}" if @debugging + unless client.nil? + new_session_client = nil + unless client.logout_flag + # Zero out client id and secret; will re-populate later when client + # is reloaded. Should be safe to store client id and secret inside + # encrypted client; however, out of an abundance of caution (and b/c + # it just makes sense), client id and secret will never be written + # to session but only stored via @endpoints variable server side. + client.client_id = nil + client.client_secret = nil + client.version = nil + client.debugging = nil + client.last_seen = Time.now + new_session_client = Gibberish::AES.new(@token_encryption_key).encrypt(Marshal.dump(client)) + end + if new_session_client != session_client + session_client_put(new_session_client) + end + end + puts "(3) client as stored in session \n#{session_client}" if @debugging + + end + + def retrieve_client_from_session + puts "==========================\nretrieve_client_from_session\n==========================\n" if @debugging + puts "(1) session_client \n#{session_client}" if @debugging + client = nil begin - access_token = decrypt(session_token) unless session_token.nil? + client = Marshal.load(Gibberish::AES.new(@token_encryption_key).decrypt(session_client)) unless session_client.nil? rescue Exception => e puts "Exception FYI" self.class._log_exception(e) end - unless access_token.nil? - puts "access_token post cookieread-decrypt-marshal #{access_token.inspect}" if @debugging - instance_url = access_token.params["instance_url"] - endpoint = access_token.options[:endpoint] - keys = @endpoints[endpoint] - puts "endpoint #{endpoint}\nkeys #{keys}" if @debugging - access_token.client = client(instance_url, keys[:key], keys[:secret]) - unless keys.nil? - @env[TOKEN_KEY] = access_token #::OAuth2::AccessToken.from_hash(client(instance_url, keys[:key], keys[:secret]),access_token_hash.dup) - @env[CLIENT_KEY] = ::Databasedotcom::Client.from_token(@env[TOKEN_KEY],@api_version) - @env[CLIENT_KEY].debugging = @debugging + unless client.nil? + keys = @endpoints[client.endpoint] + if @debugging + puts "(2) client #{client.inspect}" + puts "(3) client.endpoint #{client.endpoint}" + puts "(4) keys #{keys}" end - puts "materialized token: #{@env[TOKEN_KEY].inspect}" if @debugging - puts "materialized client: #{@env[CLIENT_KEY].inspect}" if @debugging + if keys.nil? + client = nil + else + client.client_id = keys[:key] + client.client_secret = keys[:secret] + client.version = @api_version + client.debugging = @debugging + end + puts "(5) client #{client.inspect}" if @debugging end + client end + def request + @request ||= Rack::Request.new(@env) + end + def session @env["rack.session"] ||= {} #in case session is nil @env["rack.session"] end - def session_token - session[TOKEN_KEY] + def session_client + session[CLIENT_KEY] end - def set_session_token(value) - session[TOKEN_KEY] = value + def session_client_put(value) + session[CLIENT_KEY] = value end - def aes - Gibberish::AES.new(@token_encryption_key) - end - - def encrypt(data) - aes.encrypt(Marshal.dump(data)) - end - - def decrypt(data) - Marshal.load(aes.decrypt(data)) - end - def on_path?(path) current_path.casecmp(path) == 0 end def current_path request.path_info.downcase.sub(/\/$/,'') end - def query_string - request.query_string.empty? ? "" : "?#{request.query_string}" - end - - def request - @request ||= Rack::Request.new(@env) - end - def full_host full_host = ENV['ORIGIN'] if full_host.nil? || full_host.strip.empty? full_host = URI.parse(request.url.gsub(/\?.*$/,'')) full_host.path = '' @@ -326,11 +298,11 @@ def client(site, client_id, client_secret) ::OAuth2::Client.new( client_id, client_secret, - :site => "https://#{Databasedotcom.parse_domain(site)}", + :site => "https://#{self.class.parse_domain(site)}", :authorize_url => '/services/oauth2/authorize', :token_url => '/services/oauth2/token' ) end @@ -341,17 +313,47 @@ r.finish end class << self + def parse_domain(url = nil) + unless url.nil? + url = "https://" + url if (url =~ /http[s]?:\/\//).nil? + begin + url = Addressable::URI.parse(url) + rescue Addressable::URI::InvalidURIError + url = nil + end + url = url.host unless url.nil? + url.strip! unless url.nil? + end + url = nil if url && url.strip.empty? + url + end + + def client_from_oauth_token(token) + c = nil + unless token.nil? + c = Databasedotcom::Client.new + m = token["id"].match(/\/id\/([^\/]+)\/([^\/]+)$/) + c.org_id = m[1] rescue nil + c.user_id = m[2] rescue nil + c.instance_url = token.params["instance_url"] + c.host = parse_domain(c.instance_url) + c.oauth_token = token.token + c.refresh_token = token.refresh_token + end + c + end + def _log_exception(exception) STDERR.puts "\n\n#{exception.class} (#{exception.message}):\n " + exception.backtrace.join("\n ") + "\n\n" end def sanitize_mydomain(mydomain) - mydomain = Databasedotcom.parse_domain(mydomain) + mydomain = parse_domain(mydomain) mydomain = nil unless mydomain.nil? || !mydomain.strip.empty? mydomain = mydomain.split(/\.my\.salesforce\.com/).first + ".my.salesforce.com" unless mydomain.nil? mydomain end