session.rb 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. require 'openssl'
  2. require 'rack'
  3. module ShopifyAPI
  4. class ValidationException < StandardError
  5. end
  6. class Session
  7. cattr_accessor :api_key, :secret, :myshopify_domain
  8. self.myshopify_domain = 'myshopify.com'
  9. attr_accessor :url, :token, :name, :extra
  10. class << self
  11. def setup(params)
  12. params.each { |k,value| public_send("#{k}=", value) }
  13. end
  14. def temp(domain, token, &block)
  15. session = new(domain, token)
  16. original_site = ShopifyAPI::Base.site.to_s
  17. original_token = ShopifyAPI::Base.headers['X-Shopify-Access-Token']
  18. original_session = new(original_site, original_token)
  19. begin
  20. ShopifyAPI::Base.activate_session(session)
  21. yield
  22. ensure
  23. ShopifyAPI::Base.activate_session(original_session)
  24. end
  25. end
  26. def prepare_url(url)
  27. return nil if url.blank?
  28. # remove http:// or https://
  29. url = url.strip.gsub(/\Ahttps?:\/\//, '')
  30. # extract host, removing any username, password or path
  31. shop = URI.parse("https://#{url}").host
  32. # extract subdomain of .myshopify.com
  33. if idx = shop.index(".")
  34. shop = shop.slice(0, idx)
  35. end
  36. return nil if shop.empty?
  37. "#{shop}.#{myshopify_domain}"
  38. rescue URI::InvalidURIError
  39. nil
  40. end
  41. def validate_signature(params)
  42. params = (params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params).with_indifferent_access
  43. return false unless signature = params[:hmac]
  44. calculated_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new(), secret, encoded_params_for_signature(params))
  45. Rack::Utils.secure_compare(calculated_signature, signature)
  46. end
  47. private
  48. def encoded_params_for_signature(params)
  49. params = params.except(:signature, :hmac, :action, :controller)
  50. params.map{|k,v| "#{URI.escape(k.to_s, '&=%')}=#{URI.escape(v.to_s, '&%')}"}.sort.join('&')
  51. end
  52. end
  53. def initialize(url, token = nil, extra = {})
  54. self.url = self.class.prepare_url(url)
  55. self.token = token
  56. self.extra = extra
  57. end
  58. def create_permission_url(scope, redirect_uri = nil)
  59. params = {:client_id => api_key, :scope => scope.join(',')}
  60. params[:redirect_uri] = redirect_uri if redirect_uri
  61. construct_oauth_url("authorize", params)
  62. end
  63. def request_token(params)
  64. return token if token
  65. unless self.class.validate_signature(params) && params[:timestamp].to_i > 24.hours.ago.utc.to_i
  66. raise ShopifyAPI::ValidationException, "Invalid Signature: Possible malicious login"
  67. end
  68. response = access_token_request(params['code'])
  69. if response.code == "200"
  70. self.extra = JSON.parse(response.body)
  71. self.token = extra.delete('access_token')
  72. if expires_in = extra.delete('expires_in')
  73. extra['expires_at'] = Time.now.utc.to_i + expires_in
  74. end
  75. token
  76. else
  77. raise RuntimeError, response.msg
  78. end
  79. end
  80. def shop
  81. Shop.current
  82. end
  83. def site
  84. "https://#{url}"
  85. end
  86. def valid?
  87. url.present? && token.present?
  88. end
  89. def expires_in
  90. return unless expires_at.present?
  91. [0, expires_at.to_i - Time.now.utc.to_i].max
  92. end
  93. def expires_at
  94. return unless extra.present?
  95. @expires_at ||= Time.at(extra['expires_at']).utc
  96. end
  97. def expired?
  98. return false if expires_in.nil?
  99. expires_in <= 0
  100. end
  101. private
  102. def parameterize(params)
  103. URI.escape(params.collect { |k, v| "#{k}=#{v}" }.join('&'))
  104. end
  105. def access_token_request(code)
  106. uri = URI.parse(construct_oauth_url('access_token'))
  107. https = Net::HTTP.new(uri.host, uri.port)
  108. https.use_ssl = true
  109. request = Net::HTTP::Post.new(uri.request_uri)
  110. request.set_form_data('client_id' => api_key, 'client_secret' => secret, 'code' => code)
  111. https.request(request)
  112. end
  113. def construct_oauth_url(path, query_params = {})
  114. query_string = "?#{parameterize(query_params)}" unless query_params.empty?
  115. "https://#{url}/admin/oauth/#{path}#{query_string}"
  116. end
  117. end
  118. end