session_test.rb 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. require 'test_helper'
  2. require 'timecop'
  3. class SessionTest < Test::Unit::TestCase
  4. def setup
  5. super
  6. ShopifyAPI::Session.secret = 'secret'
  7. end
  8. test "not be valid without a url" do
  9. session = ShopifyAPI::Session.new(domain: nil, token: "any-token", api_version: any_api_version)
  10. assert_not session.valid?
  11. end
  12. test "not be valid without token" do
  13. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: nil, api_version: any_api_version)
  14. assert_not session.valid?
  15. end
  16. test "not be valid without an API version" do
  17. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: "any-token", api_version: nil)
  18. assert_not session.valid?
  19. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: "any-token", api_version: ShopifyAPI::ApiVersion::NullVersion)
  20. assert_not session.valid?
  21. end
  22. test "default to base API version" do
  23. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: "any-token")
  24. assert session.valid?
  25. assert_equal session.api_version, ShopifyAPI::Base.api_version
  26. end
  27. test "can override the base API version" do
  28. different_api_version = '2020-01'
  29. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: "any-token", api_version: different_api_version)
  30. assert session.valid?
  31. assert_equal session.api_version, ShopifyAPI::ApiVersion.find_version(different_api_version)
  32. end
  33. test "be valid with any token, any url and version" do
  34. session = ShopifyAPI::Session.new(
  35. domain: "testshop.myshopify.com",
  36. token: "any-token",
  37. api_version: any_api_version
  38. )
  39. assert session.valid?
  40. end
  41. test "not raise error without params" do
  42. assert_nothing_raised do
  43. ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: "any-token", api_version: any_api_version)
  44. end
  45. end
  46. test "ignore everything but the subdomain in the shop" do
  47. assert_equal(
  48. "https://testshop.myshopify.com",
  49. ShopifyAPI::Session.new(
  50. domain: "http://user:pass@testshop.notshopify.net/path",
  51. token: "any-token",
  52. api_version: any_api_version
  53. ).site
  54. )
  55. end
  56. test "append the myshopify domain if not given" do
  57. assert_equal(
  58. "https://testshop.myshopify.com",
  59. ShopifyAPI::Session.new(domain: "testshop", token: "any-token", api_version: any_api_version).site
  60. )
  61. end
  62. test "not raise error without params" do
  63. assert_nothing_raised do
  64. ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: "any-token", api_version: any_api_version)
  65. end
  66. end
  67. test "raise error if params passed but signature omitted" do
  68. assert_raises(ShopifyAPI::ValidationException) do
  69. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: nil, api_version: any_api_version)
  70. session.request_token({'code' => 'any-code'})
  71. end
  72. end
  73. test "setup api_key and secret for all sessions" do
  74. ShopifyAPI::Session.setup(:api_key => "My test key", :secret => "My test secret")
  75. assert_equal "My test key", ShopifyAPI::Session.api_key
  76. assert_equal "My test secret", ShopifyAPI::Session.secret
  77. end
  78. test "#temp reset ShopifyAPI::Base values to original value" do
  79. session1 = ShopifyAPI::Session.new(domain: 'fakeshop.myshopify.com', token: 'token1', api_version: '2019-01')
  80. ShopifyAPI::Base.user = 'foo'
  81. ShopifyAPI::Base.password = 'bar'
  82. ShopifyAPI::Base.activate_session(session1)
  83. ShopifyAPI::Session.temp(domain: "testshop.myshopify.com", token: "any-token", api_version: :unstable) do
  84. @assigned_site = ShopifyAPI::Base.site
  85. @assigned_version = ShopifyAPI::Base.api_version
  86. @assigned_user = ShopifyAPI::Base.user
  87. @assigned_password = ShopifyAPI::Base.password
  88. end
  89. assert_equal('https://testshop.myshopify.com', @assigned_site.to_s)
  90. assert_equal('https://fakeshop.myshopify.com', ShopifyAPI::Base.site.to_s)
  91. assert_equal(ShopifyAPI::ApiVersion.new(handle: :unstable), @assigned_version)
  92. assert_equal(ShopifyAPI::ApiVersion.new(handle: '2019-01'), ShopifyAPI::Base.api_version)
  93. assert_nil(@assigned_user)
  94. assert_equal('foo', ShopifyAPI::Base.user)
  95. assert_nil(@assigned_password)
  96. assert_equal('bar', ShopifyAPI::Base.password)
  97. end
  98. test "#temp does not use basic auth values from Base.site" do
  99. ShopifyAPI::Base.site = 'https://user:pass@fakeshop.myshopify.com'
  100. ShopifyAPI::Session.temp(domain: "testshop.myshopify.com", token: "any-token", api_version: :unstable) do
  101. @assigned_site = ShopifyAPI::Base.site
  102. @assigned_user = ShopifyAPI::Base.user
  103. @assigned_password = ShopifyAPI::Base.password
  104. end
  105. assert_equal('https://testshop.myshopify.com', @assigned_site.to_s)
  106. assert_equal('https://fakeshop.myshopify.com', ShopifyAPI::Base.site.to_s)
  107. assert_nil(@assigned_user)
  108. assert_equal('user', ShopifyAPI::Base.user)
  109. assert_nil(@assigned_password)
  110. assert_equal('pass', ShopifyAPI::Base.password)
  111. end
  112. test "#with_session activates the session for the duration of the block" do
  113. session1 = ShopifyAPI::Session.new(domain: 'fakeshop.myshopify.com', token: 'token1', api_version: '2019-01')
  114. ShopifyAPI::Base.activate_session(session1)
  115. other_session = ShopifyAPI::Session.new(
  116. domain: "testshop.myshopify.com",
  117. token: "any-token",
  118. api_version: :unstable
  119. )
  120. ShopifyAPI::Session.with_session(other_session) do
  121. @assigned_site = ShopifyAPI::Base.site
  122. @assigned_version = ShopifyAPI::Base.api_version
  123. end
  124. assert_equal('https://testshop.myshopify.com', @assigned_site.to_s)
  125. assert_equal('https://fakeshop.myshopify.com', ShopifyAPI::Base.site.to_s)
  126. assert_equal(ShopifyAPI::ApiVersion.new(handle: :unstable), @assigned_version)
  127. assert_equal(ShopifyAPI::ApiVersion.new(handle: '2019-01'), ShopifyAPI::Base.api_version)
  128. end
  129. test "#with_session resets the activated session even if there an exception during the block" do
  130. session1 = ShopifyAPI::Session.new(domain: 'fakeshop.myshopify.com', token: 'token1', api_version: '2019-01')
  131. ShopifyAPI::Base.activate_session(session1)
  132. other_session = ShopifyAPI::Session.new(
  133. domain: "testshop.myshopify.com",
  134. token: "any-token",
  135. api_version: :unstable
  136. )
  137. assert_raises StandardError do
  138. ShopifyAPI::Session.with_session(other_session) { raise StandardError, "" }
  139. end
  140. assert_equal('https://fakeshop.myshopify.com', ShopifyAPI::Base.site.to_s)
  141. assert_equal(ShopifyAPI::ApiVersion.new(handle: '2019-01'), ShopifyAPI::Base.api_version)
  142. end
  143. test "#with_version will adjust the actvated api version for the duration of the block" do
  144. session1 = ShopifyAPI::Session.new(domain: 'fakeshop.myshopify.com', token: 'token1', api_version: '2019-01')
  145. ShopifyAPI::Base.activate_session(session1)
  146. ShopifyAPI::Session.with_version(:unstable) do
  147. @assigned_site = ShopifyAPI::Base.site
  148. @assigned_version = ShopifyAPI::Base.api_version
  149. end
  150. assert_equal('https://fakeshop.myshopify.com', @assigned_site.to_s)
  151. assert_equal('https://fakeshop.myshopify.com', ShopifyAPI::Base.site.to_s)
  152. assert_equal(ShopifyAPI::ApiVersion.new(handle: :unstable), @assigned_version)
  153. assert_equal(ShopifyAPI::ApiVersion.new(handle: '2019-01'), ShopifyAPI::Base.api_version)
  154. end
  155. test "create_permission_url requires redirect_uri" do
  156. ShopifyAPI::Session.setup(api_key: "My_test_key", secret: "My test secret")
  157. session = ShopifyAPI::Session.new(
  158. domain: 'http://localhost.myshopify.com',
  159. token: 'any-token',
  160. api_version: any_api_version
  161. )
  162. scope = ["write_products"]
  163. assert_raises(ArgumentError) do
  164. session.create_permission_url(scope)
  165. end
  166. end
  167. test "create_permission_url returns correct url with single scope and redirect uri" do
  168. ShopifyAPI::Session.setup(api_key: "My_test_key", secret: "My test secret")
  169. session = ShopifyAPI::Session.new(
  170. domain: 'http://localhost.myshopify.com',
  171. token: 'any-token',
  172. api_version: any_api_version
  173. )
  174. scope = ["write_products"]
  175. permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com")
  176. assert_equal "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&scope=write_products&redirect_uri=http://my_redirect_uri.com", permission_url
  177. end
  178. test "create_permission_url returns correct url with dual scope" do
  179. ShopifyAPI::Session.setup(api_key: "My_test_key", secret: "My test secret")
  180. session = ShopifyAPI::Session.new(
  181. domain: 'http://localhost.myshopify.com',
  182. token: 'any-token',
  183. api_version: any_api_version
  184. )
  185. scope = ["write_products","write_customers"]
  186. permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com")
  187. assert_equal "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&scope=write_products,write_customers&redirect_uri=http://my_redirect_uri.com", permission_url
  188. end
  189. test "create_permission_url returns correct url with no scope" do
  190. ShopifyAPI::Session.setup(api_key: "My_test_key", secret: "My test secret")
  191. session = ShopifyAPI::Session.new(
  192. domain: 'http://localhost.myshopify.com',
  193. token: 'any-token',
  194. api_version: any_api_version
  195. )
  196. scope = []
  197. permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com")
  198. assert_equal "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&scope=&redirect_uri=http://my_redirect_uri.com", permission_url
  199. end
  200. test "create_permission_url returns correct url with state" do
  201. ShopifyAPI::Session.setup(api_key: "My_test_key", secret: "My test secret")
  202. session = ShopifyAPI::Session.new(
  203. domain: 'http://localhost.myshopify.com',
  204. token: 'any-token',
  205. api_version: any_api_version
  206. )
  207. scope = []
  208. permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com", state: "My nonce")
  209. assert_equal "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&scope=&redirect_uri=http://my_redirect_uri.com&state=My%20nonce", permission_url
  210. end
  211. test "raise exception if code invalid in request token" do
  212. ShopifyAPI::Session.setup(:api_key => "My test key", :secret => "My test secret")
  213. session = ShopifyAPI::Session.new(
  214. domain: 'http://localhost.myshopify.com',
  215. token: nil,
  216. api_version: any_api_version
  217. )
  218. fake(
  219. nil,
  220. url: 'https://localhost.myshopify.com/admin/oauth/access_token',
  221. method: :post,
  222. status: 404,
  223. body: '{"error" : "invalid_request"}'
  224. )
  225. assert_raises(ShopifyAPI::ValidationException) do
  226. session.request_token(code: "bad-code")
  227. end
  228. assert_equal false, session.valid?
  229. end
  230. test "return site for session" do
  231. session = ShopifyAPI::Session.new(
  232. domain: "testshop.myshopify.com",
  233. token: "any-token",
  234. api_version: any_api_version
  235. )
  236. assert_equal "https://testshop.myshopify.com", session.site
  237. end
  238. test "return_token_if_signature_is_valid" do
  239. api_version = any_api_version
  240. fake nil,
  241. url: "https://testshop.myshopify.com/admin/oauth/access_token",
  242. method: :post,
  243. body: '{"access_token":"any-token"}'
  244. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: nil, api_version: api_version)
  245. params = { code: 'any-code', timestamp: Time.now }
  246. token = session.request_token(params.merge(hmac: generate_signature(params)))
  247. assert_equal "any-token", token
  248. assert_equal "any-token", session.token
  249. end
  250. test "extra parameters are stored in session" do
  251. api_version = ShopifyAPI::ApiVersion.new(handle: :unstable)
  252. fake nil,
  253. url: "https://testshop.myshopify.com/admin/oauth/access_token",
  254. method: :post,
  255. body: '{"access_token":"any-token","foo":"example"}'
  256. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: nil, api_version: api_version)
  257. params = { code: 'any-code', timestamp: Time.now }
  258. assert session.request_token(params.merge(hmac: generate_signature(params)))
  259. assert_equal ({ "foo" => "example" }), session.extra
  260. end
  261. test "expires_in is automatically converted in expires_at" do
  262. api_version = any_api_version
  263. fake nil,
  264. url: "https://testshop.myshopify.com/admin/oauth/access_token",
  265. method: :post,
  266. body: '{"access_token":"any-token","expires_in":86393}'
  267. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: nil, api_version: api_version)
  268. Timecop.freeze do
  269. params = { code: 'any-code', timestamp: Time.now }
  270. assert session.request_token(params.merge(hmac: generate_signature(params)))
  271. expires_at = Time.now.utc + 86393
  272. assert_equal ({ "expires_at" => expires_at.to_i }), session.extra
  273. assert session.expires_at.is_a?(Time)
  274. assert_equal expires_at.to_i, session.expires_at.to_i
  275. assert_equal 86393, session.expires_in
  276. assert_equal false, session.expired?
  277. Timecop.travel(session.expires_at) do
  278. assert_equal true, session.expired?
  279. end
  280. end
  281. end
  282. test "raise error if signature does not match expected" do
  283. params = {:code => "any-code", :timestamp => Time.now}
  284. signature = generate_signature(params)
  285. params[:foo] = 'world'
  286. assert_raises(ShopifyAPI::ValidationException) do
  287. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: nil, api_version: any_api_version)
  288. session.request_token(params.merge(:hmac => signature))
  289. end
  290. end
  291. test "raise error if timestamp is too old" do
  292. params = {:code => "any-code", :timestamp => Time.now - 2.days}
  293. signature = generate_signature(params)
  294. params[:foo] = 'world'
  295. assert_raises(ShopifyAPI::ValidationException) do
  296. session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: nil, api_version: any_api_version)
  297. session.request_token(params.merge(:hmac => signature))
  298. end
  299. end
  300. test "return true when the signature is valid and the keys of params are strings" do
  301. params = { 'code' => 'any-code', 'timestamp' => Time.now }
  302. params[:hmac] = generate_signature(params)
  303. assert_equal true, ShopifyAPI::Session.validate_signature(params)
  304. end
  305. test "return true when validating signature of params with ampersand and equal sign characters" do
  306. ShopifyAPI::Session.secret = 'secret'
  307. params = { 'a' => '1&b=2', 'c=3&d' => '4' }
  308. to_sign = 'a=1%26b=2&c%3D3%26d=4'
  309. params[:hmac] = generate_signature(to_sign)
  310. assert_equal true, ShopifyAPI::Session.validate_signature(params)
  311. end
  312. test "return true when validating signature of params with percent sign characters" do
  313. ShopifyAPI::Session.secret = 'secret'
  314. params = { 'a%3D1%26b' => '2%26c%3D3' }
  315. to_sign = 'a%253D1%2526b=2%2526c%253D3'
  316. params[:hmac] = generate_signature(to_sign)
  317. assert_equal true, ShopifyAPI::Session.validate_signature(params)
  318. end
  319. test "url is aliased to domain to minimize the upgrade changes" do
  320. session = ShopifyAPI::Session.new(
  321. domain: "http://testshop.myshopify.com",
  322. token: "any-token",
  323. api_version: any_api_version
  324. )
  325. assert_equal('testshop.myshopify.com', session.url)
  326. end
  327. test "#hash returns the same value for equal Sessions" do
  328. session = ShopifyAPI::Session.new(
  329. domain: "http://testshop.myshopify.com",
  330. token: "any-token",
  331. api_version: '2019-01',
  332. extra: { foo: "bar" }
  333. )
  334. other_session = ShopifyAPI::Session.new(
  335. domain: "http://testshop.myshopify.com",
  336. token: "any-token",
  337. api_version: '2019-01',
  338. extra: { foo: "bar" }
  339. )
  340. assert_equal(session.hash, other_session.hash)
  341. end
  342. test "equality verifies domain" do
  343. session = ShopifyAPI::Session.new(
  344. domain: "http://testshop.myshopify.com",
  345. token: "any-token",
  346. api_version: '2019-01',
  347. extra: { foo: "bar" }
  348. )
  349. other_session = ShopifyAPI::Session.new(
  350. domain: "http://testshop.myshopify.com",
  351. token: "any-token",
  352. api_version: '2019-01',
  353. extra: { foo: "bar" }
  354. )
  355. different_session = ShopifyAPI::Session.new(
  356. domain: "http://another_testshop.myshopify.com",
  357. token: "any-token",
  358. api_version: '2019-01',
  359. extra: { foo: "bar" }
  360. )
  361. assert_equal(session, other_session)
  362. refute_equal(session, different_session)
  363. end
  364. test "equality verifies token" do
  365. session = ShopifyAPI::Session.new(
  366. domain: "http://testshop.myshopify.com",
  367. token: "any-token",
  368. api_version: '2019-01',
  369. extra: { foo: "bar" }
  370. )
  371. different_session = ShopifyAPI::Session.new(
  372. domain: "http://testshop.myshopify.com",
  373. token: "very-different-token",
  374. api_version: '2019-01',
  375. extra: { foo: "bar" }
  376. )
  377. refute_equal(session, different_session)
  378. end
  379. test "equality verifies api_version" do
  380. session = ShopifyAPI::Session.new(
  381. domain: "http://testshop.myshopify.com",
  382. token: "any-token",
  383. api_version: '2019-01',
  384. extra: { foo: "bar" }
  385. )
  386. different_session = ShopifyAPI::Session.new(
  387. domain: "http://testshop.myshopify.com",
  388. token: "any-token",
  389. api_version: :unstable,
  390. extra: { foo: "bar" }
  391. )
  392. refute_equal(session, different_session)
  393. end
  394. test "equality verifies extra" do
  395. session = ShopifyAPI::Session.new(
  396. domain: "http://testshop.myshopify.com",
  397. token: "any-token",
  398. api_version: '2019-01',
  399. extra: { foo: "bar" }
  400. )
  401. different_session = ShopifyAPI::Session.new(
  402. domain: "http://testshop.myshopify.com",
  403. token: "any-token",
  404. api_version: '2019-01',
  405. extra: { bar: "other-bar" }
  406. )
  407. refute_equal(session, different_session)
  408. end
  409. test "equality verifies other is a Session" do
  410. session = ShopifyAPI::Session.new(
  411. domain: "http://testshop.myshopify.com",
  412. token: "any-token",
  413. api_version: '2019-01',
  414. extra: { foo: "bar" }
  415. )
  416. different_session = nil
  417. refute_equal(session, different_session)
  418. end
  419. test "#eql? and #hash are implemented" do
  420. session = ShopifyAPI::Session.new(
  421. domain: "http://testshop.myshopify.com",
  422. token: "any-token",
  423. api_version: '2019-01',
  424. extra: { foo: "bar" }
  425. )
  426. other_session = ShopifyAPI::Session.new(
  427. domain: "http://testshop.myshopify.com",
  428. token: "any-token",
  429. api_version: '2019-01',
  430. extra: { foo: "bar" }
  431. )
  432. different_session = ShopifyAPI::Session.new(
  433. domain: "http://another_testshop.myshopify.com",
  434. token: "any-token",
  435. api_version: '2019-01',
  436. extra: { foo: "bar" }
  437. )
  438. assert_equal([session, different_session], [session, other_session, different_session].uniq)
  439. end
  440. private
  441. def make_sorted_params(params)
  442. params.with_indifferent_access.except(
  443. :signature, :hmac, :action, :controller
  444. ).collect { |k, v| "#{k}=#{v}" }.sort.join('&')
  445. end
  446. def generate_signature(params)
  447. params = make_sorted_params(params) if params.is_a?(Hash)
  448. OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA256'), ShopifyAPI::Session.secret, params)
  449. end
  450. def any_api_version
  451. version_name = ['2019-01', :unstable].sample(1).first
  452. ShopifyAPI::ApiVersion.find_version(version_name)
  453. end
  454. end