session_test.rb 19 KB

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