| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 | require 'openssl'require 'rack'module ShopifyAPI  class ValidationException < StandardError  end  class Session    cattr_accessor :api_key, :secret, :myshopify_domain    self.myshopify_domain = 'myshopify.com'    attr_accessor :domain, :token, :name, :extra    attr_reader :api_version    alias_method :url, :domain    class << self      def setup(params)        params.each { |k,value| public_send("#{k}=", value) }      end      def temp(domain:, token:, api_version: ShopifyAPI::Base.api_version, &block)        session = new(domain: domain, token: token, api_version: api_version)        with_session(session, &block)      end      def with_session(session, &_block)        original_session = extract_current_session        original_user = ShopifyAPI::Base.user        original_password = ShopifyAPI::Base.password        begin          ShopifyAPI::Base.clear_session          ShopifyAPI::Base.activate_session(session)          yield        ensure          ShopifyAPI::Base.activate_session(original_session)          ShopifyAPI::Base.user = original_user          ShopifyAPI::Base.password = original_password        end      end      def with_version(api_version, &block)        original_session = extract_current_session        session = new(domain: original_session.site, token: original_session.token, api_version: api_version)        with_session(session, &block)      end      def prepare_domain(domain)        return nil if domain.blank?        # remove http:// or https://        domain = domain.strip.gsub(%r{\Ahttps?://}, '')        # extract host, removing any username, password or path        shop = URI.parse("https://#{domain}").host        # extract subdomain of .myshopify.com        if (idx = shop.index("."))          shop = shop.slice(0, idx)        end        return nil if shop.empty?        "#{shop}.#{myshopify_domain}"      rescue URI::InvalidURIError        nil      end      def validate_signature(params)        params = (params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params).with_indifferent_access        return false unless (signature = params[:hmac])        calculated_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA256'), secret, encoded_params_for_signature(params))        Rack::Utils.secure_compare(calculated_signature, signature)      end      private      def encoded_params_for_signature(params)        params = params.except(:signature, :hmac, :action, :controller)        params.map{|k,v| "#{URI.escape(k.to_s, '&=%')}=#{URI.escape(v.to_s, '&%')}"}.sort.join('&')      end      def extract_current_session        site = ShopifyAPI::Base.site.to_s        token = ShopifyAPI::Base.headers['X-Shopify-Access-Token']        version = ShopifyAPI::Base.api_version        new(domain: site, token: token, api_version: version)      end    end    def initialize(domain:, token:, api_version: ShopifyAPI::Base.api_version, extra: {})      self.domain = self.class.prepare_domain(domain)      self.api_version = api_version      self.token = token      self.extra = extra    end    def create_permission_url(scope, redirect_uri, options = {})      params = { client_id: api_key, scope: scope.join(','), redirect_uri: redirect_uri }      params[:state] = options[:state] if options[:state]      construct_oauth_url("authorize", params)    end    def request_token(params)      return token if token      unless self.class.validate_signature(params) && params[:timestamp].to_i > 24.hours.ago.utc.to_i        raise ShopifyAPI::ValidationException, "Invalid Signature: Possible malicious login"      end      response = access_token_request(params[:code])      if response.code == "200"        self.extra = JSON.parse(response.body)        self.token = extra.delete('access_token')        if (expires_in = extra.delete('expires_in'))          extra['expires_at'] = Time.now.utc.to_i + expires_in        end        token      else        raise RuntimeError, response.msg      end    end    def shop      Shop.current    end    def site      "https://#{domain}"    end    def api_version=(version)      @api_version = ApiVersion::NullVersion.matches?(version) ? ApiVersion::NullVersion : ApiVersion.find_version(version)    end    def valid?      domain.present? && token.present? && api_version.is_a?(ApiVersion)    end    def expires_in      return unless expires_at.present?      [0, expires_at.to_i - Time.now.utc.to_i].max    end    def expires_at      return unless extra.present?      @expires_at ||= Time.at(extra['expires_at']).utc    end    def expired?      return false if expires_in.nil?      expires_in <= 0    end    def hash      state.hash    end    def ==(other)      self.class == other.class && state == other.state    end    alias_method :eql?, :==    protected    def state      [domain, token, api_version, extra]    end    private    def parameterize(params)      URI.escape(params.collect { |k, v| "#{k}=#{v}" }.join('&'))    end    def access_token_request(code)      uri = URI.parse(construct_oauth_url('access_token'))      https = Net::HTTP.new(uri.host, uri.port)      https.use_ssl = true      request = Net::HTTP::Post.new(uri.request_uri)      request.set_form_data('client_id' => api_key, 'client_secret' => secret, 'code' => code)      https.request(request)    end    def construct_oauth_url(path, query_params = {})      query_string = "?#{parameterize(query_params)}" unless query_params.empty?      "https://#{domain}/admin/oauth/#{path}#{query_string}"    end  endend
 |