session_test.rb 19 KB

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