diff --git a/CHANGELOG.md b/CHANGELOG.md index a91129044..66a7353d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +11.5.1 +----- +* Revert per-user token support temporarily + 11.5.0 ----- * Modularizes durable session storage diff --git a/README.md b/README.md index 085d17be7..729ada14f 100644 --- a/README.md +++ b/README.md @@ -12,35 +12,61 @@ Shopify Application Rails engine and generator Table of Contents ----------------- -- [Introduction](#introduction) -- [Becoming a Shopify App Developer](#becoming-a-shopify-app-developer) -- [Installation](#installation) -- [Generators](#generators) -- [Mounting the Engine](#mounting-the-engine) -- [Authentication](#authentication) -- [WebhooksManager](#webhooksmanager) -- [ScripttagsManager](#scripttagsmanager) -- [RotateShopifyTokenJob](#rotateshopifytokenjob) -- [App Tunneling](#app-tunneling) -- [AppProxyVerification](#appproxyverification) -- [Troubleshooting](#troubleshooting) -- [Testing an embedded app outside the Shopify admin](#testing-an-embedded-app-outside-the-shopify-admin) -- [Questions or problems?](#questions-or-problems-) -- [Rails 6 Compatibility](#rails-6-compatibility) -- [Upgrading from 8.6 to 9.0.0](#upgrading-from-86-to-900) - -Introduction +* [**Description**](#description) +* [**Quickstart**](#quickstart) +* [**Becoming a Shopify App Developer**](#becoming-a-shopify-app-developer) +* [**App Tunneling**](#app-tunneling) +* [**Installation**](#installation) + * [Rails Compatibility](#rails-compatibility) +* [**Generators**](#generators) + * [Default Generator](#default-generator) + * [Install Generator](#install-generator) + * [Shop Model Generator](#shop-model-generator) + * [Home Controller Generator](#home-controller-generator) + * [App Proxy Controller Generator](#app-proxy-controller-generator) + * [Controllers, Routes and Views](#controllers-routes-and-views) +* [**Mounting the Engine**](#mounting-the-engine) +* [**WebhooksManager**](#webhooksmanager) +* [**ScripttagsManager**](#scripttagsmanager) +* [**AfterAuthenticate Job**](#afterauthenticate-job) +* [**ShopifyApp::SessionRepository**](#shopifyappsessionrepository) +* [**Authenticated**](#authenticated) +* [**AppProxyVerification**](#appproxyverification) + * [Recommended Usage](#recommended-usage) +* [**Upgrading from 8.6 to 9.0.0**](#upgrading-from-86-to-900) +* [**Troubleshooting**](#troubleshooting) + * [Generator shopify_app:install hangs](#generator-shopify_appinstall-hangs) +* [**Testing an embedded app outside the Shopify admin**](#testing-an-embedded-app-outside-the-shopify-admin) +* [**Questions or problems?**](#questions-or-problems) + + +Description ----------- This gem includes a Rails Engine and generators for writing Rails applications using the Shopify API. The Engine provides a SessionsController and all the required code for authenticating with a shop via Oauth (other authentication methods are not supported). *Note: It's recommended to use this on a new Rails project, so that the generator won't overwrite/delete some of your files.* + +Quickstart +---------- + Check out this screencast on how to create and deploy a new Shopify App to Heroku in 5 minutes: [https://www.youtube.com/watch?v=yGxeoAHlQOg](https://www.youtube.com/watch?v=yGxeoAHlQOg) Or if you prefer text instructions the steps in the video are written out [here](https://github.com/Shopify/shopify_app/blob/master/docs/Quickstart.md) +App Tunneling +------------- + +Your local app needs to be accessible from the public Internet in order to install it on a shop, use the [App Proxy Controller](#app-proxy-controller-generator) or receive Webhooks. Use a tunneling service like [ngrok](https://ngrok.com/), [Forward](https://forwardhq.com/), [Beeceptor](https://beeceptor.com/), [Mockbin](http://mockbin.org/), [Hookbin](https://hookbin.com/), etc. + +For example with [ngrok](https://ngrok.com/), run this command to set up proxying to Rails' default port: + +```sh +ngrok http 3000 +``` + Becoming a Shopify App Developer -------------------------------- If you don't have a Shopify Partner account yet head over to http://shopify.com/partners to create one, you'll need it before you can start developing apps. @@ -80,7 +106,7 @@ The default generator will run the `install`, `shop`, and `home_controller` gene $ rails generate shopify_app ``` -After running the generator, you will need to run `rails db:migrate` to add tables to your database. You can start your app with `bundle exec rails server` and install your app by visiting localhost. +After running the generator, you will need to run `rake db:migrate` to add tables to your database. You can start your app with `bundle exec rails server` and install your app by visiting localhost. ### API Keys @@ -117,6 +143,17 @@ The generator adds ShopifyApp and the required initializers to the host Rails ap After running the `install` generator, you can start your app with `bundle exec rails server` and install your app by visiting localhost. +### Shop Model Generator + +```sh +$ rails generate shopify_app:shop_model +``` + +The `install` generator doesn't create any database tables or models for you. If you are starting a new app its quite likely that you will want a shops table and model to store the tokens when your app is installed (most of our internally developed apps do!). This generator creates a shop model and a migration. This model includes the `ShopifyApp::SessionStorage` concern which adds two methods to make it compatible as a `SessionRepository`. After running this generator you'll notice the `session_repository` in your `config/initializers/shopify_app.rb` will be set to the `Shop` model. This means that internally ShopifyApp will try and load tokens from this model. + +*Note that you will need to run rake db:migrate after this generator* + + ### Home Controller Generator ```sh @@ -208,82 +245,21 @@ ShopifyApp.configure do |config| end ``` -Authentication --------------- - -### ShopifyApp::SessionRepository - -`ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are stored and retrieved for shops. The `SessionRepository` is configured in the `config/initializers/shopify_app.rb` file and can be set to any object that implements `self.store(auth_session)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. See either the `ShopifyApp::InMemorySessionStore` class or the `ShopifyApp::SessionStorage` concern for details. - -If you only run the install generator then by default you will have an in memory store but it **won't work** on multi-server environments including Heroku. For multi-server environments, implement one of the following token-storage strategies. - -#### Shop-based token storage -Storing tokens on the store model means that any user login associated to the store will have equal access levels to whatever the original user granted the app. -```sh -$ rails generate shopify_app:shop_model -``` -This will generate a shop model which will be the storage for the tokens necessary for authentication. - -#### User-based token storage -A more granular control over level of access per user on an app might be necessary, to which the shop-based token strategy is not sufficient. Shopify supports a user-based token storage strategy where a unique token to each user can be managed. -```sh -$ rails generate shopify_app:user_model -``` -This will generate a user model which will be the storage for the tokens necessary for authentication. - -The current Shopify user will be stored in the rails session at `session[:shopify_user]` - -This will change the type of token that Shopify returns and it will only be valid for a short time. Read more about `Online access` [here](https://help.shopify.com/api/getting-started/authentication/oauth). Note that this means you won't be able to use this token to respond to Webhooks. - -#### Migrating from shop-based to user-based token strategy -After running the generator, ensure that configuration settings are successfully changed: +Per User Authentication +----------------------- +To enable per user authentication you need to update the `omniauth.rb` initializer: ```ruby -# In the `omniauth.rb` initializer: provider :shopify, ShopifyApp.configuration.api_key, ShopifyApp.configuration.secret, scope: ShopifyApp.configuration.scope, per_user_permissions: true - -# In the `shopify_app.rb` initializer: -config.session_repository = 'User' -config.per_user_tokens = true -``` - -### Authenticated - -The engine provides a `ShopifyApp::Authenticated` concern which should be included in any controller that is intended to be behind Shopify OAuth. It adds `before_action`s to ensure that the user is authenticated and will redirect to the Shopify login page if not. It is best practice to include this concern in a base controller inheriting from your `ApplicationController`, from which all controllers that require Shopify authentication inherit. - -For backwards compatibility, the engine still provides a controller called `ShopifyApp::AuthenticatedController` which includes the `ShopifyApp::Authenticated` concern. Note that it inherits directly from `ActionController::Base`, so you will not be able to share functionality between it and your application's `ApplicationController`. - -### AfterAuthenticate Job - -If your app needs to perform specific actions after the user is authenticated successfully (i.e. every time a new session is created), ShopifyApp can queue or run a job of your choosing (note that we already provide support for automatically creating Webhooks and Scripttags). To configure the after authenticate job update your initializer as follows: - -```ruby -ShopifyApp.configure do |config| - config.after_authenticate_job = { job: "Shopify::AfterAuthenticateJob" } -end ``` -The job can be configured as either a class or a class name string. - -If you need the job to run synchronously add the `inline` flag: - -```ruby -ShopifyApp.configure do |config| - config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob, inline: true } -end -``` - -We've also provided a generator which creates a skeleton job and updates the initializer for you: - -``` -bin/rails g shopify_app:add_after_authenticate_job -``` +The current Shopify user will be stored in the rails session at `session[:shopify_user]` -If you want to perform that action only once, e.g. send a welcome email to the user when they install the app, you should make sure that this action is idempotent, meaning that it won't have an impact if run multiple times. +This will change the type of token that Shopify returns and it will only be valid for a short time. Read more about `Online access` [here](https://help.shopify.com/api/getting-started/authentication/oauth). Note that this means you won't be able to use this token to respond to Webhooks. WebhooksManager @@ -377,6 +353,36 @@ Scripttags are created in the same way as the Webhooks, with a background job wh If `src` responds to `call` its return value will be used as the scripttag's source. It will be called on scripttag creation and deletion. +AfterAuthenticate Job +--------------------- + +If your app needs to perform specific actions after the user is authenticated successfully (i.e. every time a new session is created), ShopifyApp can queue or run a job of your choosing (note that we already provide support for automatically creating Webhooks and Scripttags). To configure the after authenticate job update your initializer as follows: + +```ruby +ShopifyApp.configure do |config| + config.after_authenticate_job = { job: "Shopify::AfterAuthenticateJob" } +end +``` + +The job can be configured as either a class or a class name string. + +If you need the job to run synchronously add the `inline` flag: + +```ruby +ShopifyApp.configure do |config| + config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob, inline: true } +end +``` + +We've also provided a generator which creates a skeleton job and updates the initializer for you: + +``` +bin/rails g shopify_app:add_after_authenticate_job +``` + +If you want to perform that action only once, e.g. send a welcome email to the user when they install the app, you should make sure that this action is idempotent, meaning that it won't have an impact if run multiple times. + + RotateShopifyTokenJob --------------------- @@ -403,16 +409,19 @@ The generated rake task will be found at `lib/tasks/shopify/rotate_shopify_token strategy.options[:old_client_secret] = ShopifyApp.configuration.old_secret ``` -App Tunneling -------------- +ShopifyApp::SessionRepository +----------------------------- -Your local app needs to be accessible from the public Internet in order to install it on a shop, use the [App Proxy Controller](#app-proxy-controller-generator) or receive Webhooks. Use a tunneling service like [ngrok](https://ngrok.com/), [Forward](https://forwardhq.com/), [Beeceptor](https://beeceptor.com/), [Mockbin](http://mockbin.org/), [Hookbin](https://hookbin.com/), etc. +`ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are retrieved and stored for shops. The `SessionRepository` is configured in the `config/initializers/shopify_app.rb` file and can be set to any object that implements `self.store(shopify_session)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. See either the `ShopifyApp::InMemorySessionStore` class or the `ShopifyApp::SessionStorage` concern for examples. -For example with [ngrok](https://ngrok.com/), run this command to set up proxying to Rails' default port: +If you only run the install generator then by default you will have an in memory store but it **won't work** on multi-server environments including Heroku. If you ran all the generators including the shop_model generator then the `Shop` model itself will be the `SessionRepository`. If you look at the implementation of the generated shop model you'll see that this gem provides a concern for the `SessionRepository`. You can use this concern on any model that responds to `shopify_domain`, `shopify_token` and `api_version`. -```sh -ngrok http 3000 -``` +Authenticated +------------- + +The engine provides a `ShopifyApp::Authenticated` concern which should be included in any controller that is intended to be behind Shopify OAuth. It adds `before_action`s to ensure that the user is authenticated and will redirect to the Shopify login page if not. It is best practice to include this concern in a base controller inheriting from your `ApplicationController`, from which all controllers that require Shopify authentication inherit. + +For backwards compatibility, the engine still provides a controller called `ShopifyApp::AuthenticatedController` which includes the `ShopifyApp::Authenticated` concern. Note that it inherits directly from `ActionController::Base`, so you will not be able to share functionality between it and your application's `ApplicationController`. AppProxyVerification -------------------- @@ -456,7 +465,7 @@ Questions or problems? - [Read the docs!](https://help.shopify.com/api/guides) Rails 6 Compatibility ---------------------- +--------------------------- ### Disable Webpacker If you are using sprockets in rails 6 or want to generate a shopify_app without webpacker run the install task by running diff --git a/app/controllers/concerns/shopify_app/authenticated.rb b/app/controllers/concerns/shopify_app/authenticated.rb index 7ac447edf..ba3c6fa8d 100644 --- a/app/controllers/concerns/shopify_app/authenticated.rb +++ b/app/controllers/concerns/shopify_app/authenticated.rb @@ -8,7 +8,7 @@ module Authenticated include ShopifyApp::Localization include ShopifyApp::LoginProtection include ShopifyApp::EmbeddedApp - before_action :login_again_if_different_user_or_shop + before_action :login_again_if_different_shop around_action :shopify_session end end diff --git a/app/controllers/shopify_app/callback_controller.rb b/app/controllers/shopify_app/callback_controller.rb index e959b68c9..db0ab63fa 100644 --- a/app/controllers/shopify_app/callback_controller.rb +++ b/app/controllers/shopify_app/callback_controller.rb @@ -55,16 +55,10 @@ def set_shopify_session token: token, api_version: ShopifyApp.configuration.api_version ) - session[:shopify] = ShopifyApp::SessionRepository.store(session_store, user: associated_user) + + session[:shopify] = ShopifyApp::SessionRepository.store(session_store) session[:shopify_domain] = shop_name session[:shopify_user] = associated_user - - if ShopifyApp.configuration.per_user_tokens? - # Adds the user_session to the session to determine if the logged in user has changed - user_session = auth_hash&.extra&.session - raise IndexError, "Missing user session signature" if user_session.nil? - session[:user_session] = user_session - end end def install_webhooks diff --git a/lib/generators/shopify_app/install/templates/shopify_provider.rb b/lib/generators/shopify_app/install/templates/shopify_provider.rb index a90ac7f55..b12cc2750 100644 --- a/lib/generators/shopify_app/install/templates/shopify_provider.rb +++ b/lib/generators/shopify_app/install/templates/shopify_provider.rb @@ -4,7 +4,6 @@ ShopifyApp.configuration.api_key, ShopifyApp.configuration.secret, scope: ShopifyApp.configuration.scope, - per_user_permissions: ShopifyApp.configuration.per_user_tokens, setup: lambda { |env| strategy = env['omniauth.strategy'] diff --git a/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb b/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb deleted file mode 100644 index aa8b71f2d..000000000 --- a/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb +++ /dev/null @@ -1,16 +0,0 @@ -class CreateUsers < ActiveRecord::Migration[<%= rails_migration_version %>] - def self.up - create_table :users do |t| - t.bigint :shopify_user_id, null: false - t.string :shopify_domain, null: false - t.string :shopify_token, null: false - t.timestamps - end - - add_index :users, :shopify_user_id, unique: true - end - - def self.down - drop_table :users - end -end diff --git a/lib/generators/shopify_app/user_model/templates/user.rb b/lib/generators/shopify_app/user_model/templates/user.rb deleted file mode 100644 index 9ded5255a..000000000 --- a/lib/generators/shopify_app/user_model/templates/user.rb +++ /dev/null @@ -1,7 +0,0 @@ -class User < ActiveRecord::Base - include ShopifyApp::SessionStorage - - def api_version - ShopifyApp.configuration.api_version - end -end diff --git a/lib/generators/shopify_app/user_model/templates/users.yml b/lib/generators/shopify_app/user_model/templates/users.yml deleted file mode 100644 index 2322f5643..000000000 --- a/lib/generators/shopify_app/user_model/templates/users.yml +++ /dev/null @@ -1,4 +0,0 @@ -regular_user: - shopify_domain: 'regular-shop.myshopify.com' - shopify_token: 'token' - shopify_user_id: 1 diff --git a/lib/generators/shopify_app/user_model/user_model_generator.rb b/lib/generators/shopify_app/user_model/user_model_generator.rb deleted file mode 100644 index d65794119..000000000 --- a/lib/generators/shopify_app/user_model/user_model_generator.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'rails/generators/base' -require 'rails/generators/active_record' - -module ShopifyApp - module Generators - class UserModelGenerator < Rails::Generators::Base - include Rails::Generators::Migration - source_root File.expand_path('../templates', __FILE__) - - def create_user_model - copy_file 'user.rb', 'app/models/user.rb' - end - - def create_user_migration - migration_template 'db/migrate/create_users.erb', 'db/migrate/create_users.rb' - end - - def update_shopify_app_initializer - gsub_file 'config/initializers/shopify_app.rb', 'ShopifyApp::InMemorySessionStore', 'User' - end - - def create_user_fixtures - copy_file 'users.yml', 'test/fixtures/users.yml' - end - - private - - def rails_migration_version - Rails.version.match(/\d\.\d/)[0] - end - - # for generating a timestamp when using `create_migration` - def self.next_migration_number(dir) - ActiveRecord::Generators::Base.next_migration_number(dir) - end - end - end -end diff --git a/lib/shopify_app.rb b/lib/shopify_app.rb index dda339bb5..6f8a119d1 100644 --- a/lib/shopify_app.rb +++ b/lib/shopify_app.rb @@ -44,8 +44,6 @@ def self.use_webpacker? require 'shopify_app/managers/scripttags_manager' # session - require 'shopify_app/session/storage_strategies/shop_storage_strategy' - require 'shopify_app/session/storage_strategies/user_storage_strategy' require 'shopify_app/session/session_storage' require 'shopify_app/session/session_repository' require 'shopify_app/session/in_memory_session_store' diff --git a/lib/shopify_app/configuration.rb b/lib/shopify_app/configuration.rb index 9cf2cdfbf..f82021d10 100644 --- a/lib/shopify_app/configuration.rb +++ b/lib/shopify_app/configuration.rb @@ -15,8 +15,6 @@ class Configuration attr_accessor :scripttags attr_accessor :after_authenticate_job attr_reader :session_repository - attr_accessor :per_user_tokens - alias_method :per_user_tokens?, :per_user_tokens attr_accessor :api_version # customise urls @@ -41,7 +39,6 @@ def initialize @myshopify_domain = 'myshopify.com' @scripttags_manager_queue_name = Rails.application.config.active_job.queue_name @webhooks_manager_queue_name = Rails.application.config.active_job.queue_name - @per_user_tokens = false @disable_webpacker = ENV['SHOPIFY_APP_DISABLE_WEBPACKER'].present? end @@ -61,7 +58,6 @@ def has_webhooks? def has_scripttags? scripttags.present? end - end def self.configuration diff --git a/lib/shopify_app/controller_concerns/login_protection.rb b/lib/shopify_app/controller_concerns/login_protection.rb index 3e139b7f2..9f1deafa2 100644 --- a/lib/shopify_app/controller_concerns/login_protection.rb +++ b/lib/shopify_app/controller_concerns/login_protection.rb @@ -27,30 +27,12 @@ def shopify_session end def shop_session - if ShopifyApp.configuration.per_user_tokens? - return unless session[:shopify_user] - @shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify_user]['id']) - else - return unless session[:shopify] - @shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify]) - end + return unless session[:shopify] + @shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify]) end - def login_again_if_different_user_or_shop - if ShopifyApp.configuration.per_user_tokens? - valid_session_data = session[:user_session].present? && params[:session].present? # session data was sent/stored correctly - sessions_do_not_match = session[:user_session] != params[:session] # current user is different from stored user - - if valid_session_data && sessions_do_not_match - clear_session = true - end - end - + def login_again_if_different_shop if shop_session && params[:shop] && params[:shop].is_a?(String) && (shop_session.domain != params[:shop]) - clear_session = true - end - - if clear_session clear_shop_session redirect_to_login end @@ -78,7 +60,6 @@ def clear_shop_session session[:shopify] = nil session[:shopify_domain] = nil session[:shopify_user] = nil - session[:user_session] = nil end def login_url_with_optional_shop(top_level: false) diff --git a/lib/shopify_app/session/in_memory_session_store.rb b/lib/shopify_app/session/in_memory_session_store.rb index c7c928bac..1c212b8f9 100644 --- a/lib/shopify_app/session/in_memory_session_store.rb +++ b/lib/shopify_app/session/in_memory_session_store.rb @@ -6,7 +6,7 @@ def self.retrieve(id) repo[id] end - def self.store(session, *args) + def self.store(session) id = SecureRandom.uuid repo[id] = session id diff --git a/lib/shopify_app/session/session_repository.rb b/lib/shopify_app/session/session_repository.rb index 84fb93e42..46c333e9b 100644 --- a/lib/shopify_app/session/session_repository.rb +++ b/lib/shopify_app/session/session_repository.rb @@ -15,8 +15,8 @@ def retrieve(id) storage.retrieve(id) end - def store(session, *args) - storage.store(session, *args) + def store(session) + storage.store(session) end def storage diff --git a/lib/shopify_app/session/session_storage.rb b/lib/shopify_app/session/session_storage.rb index 408776bf7..913ad5dff 100644 --- a/lib/shopify_app/session/session_storage.rb +++ b/lib/shopify_app/session/session_storage.rb @@ -3,12 +3,9 @@ module SessionStorage extend ActiveSupport::Concern included do + validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false } validates :shopify_token, presence: true validates :api_version, presence: true - validates :shopify_domain, presence: true, - if: Proc.new {|_| ShopifyApp.configuration.per_user_tokens? } - validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }, - if: Proc.new {|_| !ShopifyApp.configuration.per_user_tokens? } end def with_shopify_session(&block) @@ -21,19 +18,23 @@ def with_shopify_session(&block) end class_methods do - - def strategy_klass - ShopifyApp.configuration.per_user_tokens? ? - ShopifyApp::SessionStorage::UserStorageStrategy : - ShopifyApp::SessionStorage::ShopStorageStrategy - end - - def store(auth_session, user: nil) - strategy_klass.store(auth_session, user) + def store(session) + shop = find_or_initialize_by(shopify_domain: session.domain) + shop.shopify_token = session.token + shop.save! + shop.id end def retrieve(id) - strategy_klass.retrieve(id) + return unless id + + if shop = self.find_by(id: id) + ShopifyAPI::Session.new( + domain: shop.shopify_domain, + token: shop.shopify_token, + api_version: shop.api_version + ) + end end end end diff --git a/lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb b/lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb deleted file mode 100644 index 5d41cd055..000000000 --- a/lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb +++ /dev/null @@ -1,24 +0,0 @@ -module ShopifyApp - module SessionStorage - class ShopStorageStrategy - - def self.store(auth_session, *args) - shop = Shop.find_or_initialize_by(shopify_domain: auth_session.domain) - shop.shopify_token = auth_session.token - shop.save! - shop.id - end - - def self.retrieve(id) - return unless id - if shop = Shop.find_by(id: id) - ShopifyAPI::Session.new( - domain: shop.shopify_domain, - token: shop.shopify_token, - api_version: shop.api_version - ) - end - end - end - end -end diff --git a/lib/shopify_app/session/storage_strategies/user_storage_strategy.rb b/lib/shopify_app/session/storage_strategies/user_storage_strategy.rb deleted file mode 100644 index e4cc5b923..000000000 --- a/lib/shopify_app/session/storage_strategies/user_storage_strategy.rb +++ /dev/null @@ -1,26 +0,0 @@ -module ShopifyApp - module SessionStorage - class UserStorageStrategy - - def self.store(auth_session, user) - user = User.find_or_initialize_by(shopify_user_id: user[:id]) - user.shopify_token = auth_session.token - user.shopify_domain = auth_session.domain - user.save! - user.id - end - - def self.retrieve(id) - return unless id - if user = User.find_by(shopify_user_id: id) - ShopifyAPI::Session.new( - domain: user.shopify_domain, - token: user.shopify_token, - api_version: user.api_version - ) - end - end - - end - end -end diff --git a/lib/shopify_app/version.rb b/lib/shopify_app/version.rb index 48ab93876..b52b49c02 100644 --- a/lib/shopify_app/version.rb +++ b/lib/shopify_app/version.rb @@ -1,3 +1,3 @@ module ShopifyApp - VERSION = '11.5.0'.freeze + VERSION = '11.5.1'.freeze end diff --git a/service.yml b/service.yml index eb7c89d36..5e25ce3e9 100644 --- a/service.yml +++ b/service.yml @@ -2,6 +2,6 @@ audience: partner classification: library org_line: App & Partner Platform owners: - - Shopify/platform-dev-tools-education + - Shopify/app-partner-dev-tools-education slack_channels: - dev-tools-education diff --git a/shopify_app.gemspec b/shopify_app.gemspec index 853f87c41..8fec2e377 100644 --- a/shopify_app.gemspec +++ b/shopify_app.gemspec @@ -18,9 +18,6 @@ Gem::Specification.new do |s| s.add_development_dependency('rake') s.add_development_dependency('byebug') s.add_development_dependency('pry') - s.add_development_dependency('pry-nav') - s.add_development_dependency('pry-stack_explorer') - s.add_development_dependency('rb-readline') s.add_development_dependency('sqlite3', '~> 1.4') s.add_development_dependency('minitest') s.add_development_dependency('mocha') @@ -29,4 +26,4 @@ Gem::Specification.new do |s| s.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(test|example)/}) } s.test_files = `git ls-files -- {test}/*`.split("\n") s.require_paths = ["lib"] -end \ No newline at end of file +end diff --git a/test/controllers/callback_controller_test.rb b/test/controllers/callback_controller_test.rb index cbe6dd91c..472e30360 100644 --- a/test/controllers/callback_controller_test.rb +++ b/test/controllers/callback_controller_test.rb @@ -50,19 +50,12 @@ class CallbackControllerTest < ActionController::TestCase end test '#callback sets up a shopify session with a user for online mode' do - begin - ShopifyApp.configuration.per_user_tokens = true - - mock_shopify_user_omniauth - - get :callback, params: { shop: 'shop' } - assert_not_nil session[:shopify] - assert_equal 'shop.myshopify.com', session[:shopify_domain] - assert_equal 'user_object', session[:shopify_user] - assert_equal 'this.is.a.user.session', session[:user_session] - ensure - ShopifyApp.configuration.per_user_tokens = false - end + mock_shopify_user_omniauth + + get :callback, params: { shop: 'shop' } + assert_not_nil session[:shopify] + assert_equal 'shop.myshopify.com', session[:shopify_domain] + assert_equal 'user_object', session[:shopify_user] end test '#callback starts the WebhooksManager if webhooks are configured' do @@ -168,12 +161,7 @@ def mock_shopify_user_omniauth provider: :shopify, uid: 'shop.myshopify.com', credentials: { token: '1234' }, - extra: { - associated_user: 'user_object', - associated_user_scope: "read_products", - scope: "read_products", - session: "this.is.a.user.session" - } + extra: { associated_user: 'user_object' } ) request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:shopify] if request request.env['omniauth.params'] = { shop: 'shop.myshopify.com' } if request diff --git a/test/generators/user_model_generator_test.rb b/test/generators/user_model_generator_test.rb deleted file mode 100644 index 7476c8eb4..000000000 --- a/test/generators/user_model_generator_test.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'test_helper' -require 'generators/shopify_app/user_model/user_model_generator' - -class UserModelGeneratorTest < Rails::Generators::TestCase - tests ShopifyApp::Generators::UserModelGenerator - destination File.expand_path("../tmp", File.dirname(__FILE__)) - - setup do - prepare_destination - provide_existing_initializer_file - end - - test "create the user model" do - run_generator - assert_file "app/models/user.rb" do |user| - assert_match "class User < ActiveRecord::Base", user - assert_match "include ShopifyApp::SessionStorage", user - assert_match(/def api_version\n\s*ShopifyApp\.configuration\.api_version\n\s*end/, user) - end - end - - test "creates UserModel migration" do - run_generator - assert_migration "db/migrate/create_users.rb" do |migration| - assert_match "create_table :users do |t|", migration - end - end - - test "updates the shopify_app initializer to use User to store session" do - run_generator - assert_file "config/initializers/shopify_app.rb" do |file| - assert_match "config.session_repository = 'User'", file - end - end - - test "creates default user fixtures" do - run_generator - assert_file "test/fixtures/users.yml" do |file| - assert_match "regular_user:", file - end - end - -end diff --git a/test/shopify_app/configuration_test.rb b/test/shopify_app/configuration_test.rb index c69958282..d14c5bcab 100644 --- a/test/shopify_app/configuration_test.rb +++ b/test/shopify_app/configuration_test.rb @@ -20,22 +20,6 @@ class ConfigurationTest < ActiveSupport::TestCase assert_equal false, ShopifyApp.configuration.after_authenticate_job end - test "configure object defaults to shop tokens" do - assert_equal false, ShopifyApp.configuration.per_user_tokens? - end - - test "configure object can set per-user tokens" do - begin - ShopifyApp.configure do |config| - config.per_user_tokens = true - end - - assert_equal true, ShopifyApp.configuration.per_user_tokens? - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - test "defaults login_url" do assert_equal "/login", ShopifyApp.configuration.login_url end diff --git a/test/shopify_app/controller_concerns/login_protection_test.rb b/test/shopify_app/controller_concerns/login_protection_test.rb index 3445491e6..6bac4799a 100644 --- a/test/shopify_app/controller_concerns/login_protection_test.rb +++ b/test/shopify_app/controller_concerns/login_protection_test.rb @@ -9,7 +9,7 @@ class LoginProtectionController < ActionController::Base helper_method :shop_session around_action :shopify_session, only: [:index] - before_action :login_again_if_different_user_or_shop, only: [:second_login] + before_action :login_again_if_different_shop, only: [:second_login] def index render plain: "OK" @@ -62,36 +62,6 @@ class LoginProtectionTest < ActionController::TestCase end end - test "#shop_session retrieves using shopify_user_id when configured for per-user tokens" do - begin - ShopifyApp.configuration.per_user_tokens = true - with_application_test_routes do - session[:shopify] = "foobar" - session[:shopify_user] = { 'id' => 'shopify_user_id', 'email' => 'foo@example.com' } - get :index - ShopifyApp::SessionRepository.expects(:retrieve).with(session[:shopify_user]['id']).returns(session).once - assert @controller.shop_session - end - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - - test "#shop_session retrieves using shop_id when configured for per-shop tokens" do - begin - ShopifyApp.configuration.per_user_tokens = false - with_application_test_routes do - session[:shopify] = "shopify_id" - session[:shopify_user] = { 'id' => 'shopify_user_id', 'email' => 'foo@example.com' } - get :index - ShopifyApp::SessionRepository.expects(:retrieve).with(session[:shopify]).returns(session).once - assert @controller.shop_session - end - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - test "#shop_session retreives the session from storage" do with_application_test_routes do session[:shopify] = "foobar" @@ -111,66 +81,7 @@ class LoginProtectionTest < ActionController::TestCase end end - test "#login_again_if_different_user_or_shop removes current session if the user changes when in per-user-token mode" do - begin - ShopifyApp.configuration.per_user_tokens = true - with_application_test_routes do - session[:shopify] = "1" - session[:shopify_domain] = "foobar" - session[:shopify_user] = { 'id' => 1, 'email' => 'foo@example.com' } - session[:user_session] = 'old-user-session' - params = { shop: 'foobar', session: 'new-user-session' } - get :second_login, params: params - assert_nil session[:shopify] - assert_nil session[:shopify_domain] - assert_nil session[:shopify_user] - assert_nil session[:user_session] - end - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - - test "#login_again_if_different_user_or_shop retains current session if the users session doesn't change" do - begin - ShopifyApp.configuration.per_user_tokens = true - with_application_test_routes do - session[:shopify] = "1" - session[:shopify_domain] = "foobar" - session[:shopify_user] = { 'id' => 1, 'email' => 'foo@example.com' } - session[:user_session] = 'old-user-session' - params = { shop: 'foobar', session: 'old-user-session' } - get :second_login, params: params - assert session[:shopify], "1" - assert session[:shopify_domain], "foobar" - assert session[:shopify_user], { 'id' => 1, 'email' => 'foo@example.com' } - assert session[:user_session], 'old-user-session' - end - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - - test "#login_again_if_different_user_or_shop retains current session if params not present" do - begin - ShopifyApp.configuration.per_user_tokens = true - with_application_test_routes do - session[:shopify] = "1" - session[:shopify_domain] = "foobar" - session[:shopify_user] = { 'id' => 1, 'email' => 'foo@example.com' } - session[:user_session] = 'old-user-session' - get :second_login - assert session[:shopify], "1" - assert session[:shopify_domain], "foobar" - assert session[:shopify_user], { 'id' => 1, 'email' => 'foo@example.com' } - assert session[:user_session], 'old-user-session' - end - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - - test "#login_again_if_different_user_or_shop removes current session and redirects to login url" do + test "#login_again_if_different_shop removes current session and redirects to login url" do with_application_test_routes do session[:shopify] = "foobar" session[:shopify_domain] = "foobar" @@ -185,7 +96,7 @@ class LoginProtectionTest < ActionController::TestCase end end - test "#login_again_if_different_user_or_shop ignores non-String shop params so that Rails params for Shop model can be accepted" do + test "#login_again_if_different_shop ignores non-String shop params so that Rails params for Shop model can be accepted" do with_application_test_routes do session[:shopify] = "foobar" session[:shopify_domain] = "foobar" diff --git a/test/shopify_app/session/shopify_session_repository_test.rb b/test/shopify_app/session/shopify_session_repository_test.rb index f07f19d5b..5b86f4191 100644 --- a/test/shopify_app/session/shopify_session_repository_test.rb +++ b/test/shopify_app/session/shopify_session_repository_test.rb @@ -26,11 +26,6 @@ def self.retrieve(id) end end -class MockSessionStore < ActiveRecord::Base - include ShopifyApp::SessionStorage -end - - class ShopifySessionRepositoryTest < ActiveSupport::TestCase attr_reader :session_store, :session @@ -79,22 +74,4 @@ class ShopifySessionRepositoryTest < ActiveSupport::TestCase assert_equal TestSessionStoreClass, ShopifyApp::SessionRepository.storage end - test "session store picks correct session strategy for per-store tokens" do - begin - ShopifyApp.configuration.per_user_tokens = false - assert_equal MockSessionStore.strategy_klass, ShopifyApp::SessionStorage::ShopStorageStrategy - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - - test "session store picks correct session strategy for per-users tokens" do - begin - ShopifyApp.configuration.per_user_tokens = true - assert_equal MockSessionStore.strategy_klass, ShopifyApp::SessionStorage::UserStorageStrategy - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - end diff --git a/test/shopify_app/session/storage_strategies/shop_storage_strategy_test.rb b/test/shopify_app/session/storage_strategies/shop_storage_strategy_test.rb deleted file mode 100644 index 3d2a285d3..000000000 --- a/test/shopify_app/session/storage_strategies/shop_storage_strategy_test.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'test_helper' - - -module ShopifyApp - class ShopStorageStrategyTest < ActiveSupport::TestCase - - test "tests that session store can retrieve shop session records" do - TEST_SHOPIFY_DOMAIN = "example.myshopify.com" - TEST_SHOPIFY_TOKEN = "1234567890qwertyuiop" - - mock_shop_class = Object.new - - mock_shop_class.stubs(:find_by).returns(MockShopInstance.new( - shopify_domain:TEST_SHOPIFY_DOMAIN, - shopify_token:TEST_SHOPIFY_TOKEN - )) - ShopifyApp::SessionStorage::ShopStorageStrategy.const_set("Shop", mock_shop_class) - - begin - ShopifyApp.configuration.per_user_tokens = false - session = MockSessionStore.retrieve(id=1) - - assert_equal TEST_SHOPIFY_DOMAIN, session.domain - assert_equal TEST_SHOPIFY_TOKEN, session.token - ensure - ShopifyApp.configuration.per_user_tokens = false - end - - ShopifyApp::SessionStorage::ShopStorageStrategy.send(:remove_const , "Shop") - end - - test "tests that session store can store shop session records" do - mock_shop_instance = MockShopInstance.new(id:12345) - mock_shop_instance.stubs(:save!).returns(true) - - mock_shop_class = Object.new - mock_shop_class.stubs(:find_or_initialize_by).returns(mock_shop_instance) - - ShopifyApp::SessionStorage::ShopStorageStrategy.const_set("Shop", mock_shop_class) - - begin - ShopifyApp.configuration.per_user_tokens = false - - mock_auth_hash = mock() - mock_auth_hash.stubs(:domain).returns(mock_shop_instance.shopify_domain) - mock_auth_hash.stubs(:token).returns("a-new-token!") - saved_id = MockSessionStore.store(mock_auth_hash) - - assert_equal "a-new-token!", mock_shop_instance.shopify_token - assert_equal mock_shop_instance.id, saved_id - - ensure - ShopifyApp.configuration.per_user_tokens = false - end - ShopifyApp::SessionStorage::ShopStorageStrategy.send(:remove_const , "Shop") - end - end -end diff --git a/test/shopify_app/session/storage_strategies/user_storage_strategy_test.rb b/test/shopify_app/session/storage_strategies/user_storage_strategy_test.rb deleted file mode 100644 index 79d6543a3..000000000 --- a/test/shopify_app/session/storage_strategies/user_storage_strategy_test.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'test_helper' - - -class MockSessionStore < ActiveRecord::Base - include ShopifyApp::SessionStorage -end - - -module ShopifyApp - class UserStorageStrategyTest < ActiveSupport::TestCase - - test "tests that UserStorageStrategy is used for session storage" do - begin - ShopifyApp.configuration.per_user_tokens = true - assert_equal MockSessionStore.strategy_klass, ShopifyApp::SessionStorage::UserStorageStrategy - ensure - ShopifyApp.configuration.per_user_tokens = false - end - end - - test "tests that session store can retrieve user session records" do - TEST_SHOPIFY_USER_ID = 42 - TEST_SHOPIFY_DOMAIN = "example.myshopify.com" - TEST_SHOPIFY_USER_TOKEN = "some-user-token-42" - - mock_user_class = Object.new - - mock_user_class.stubs(:find_by).returns(MockUserInstance.new( - shopify_user_id:TEST_SHOPIFY_USER_ID, - shopify_domain:TEST_SHOPIFY_DOMAIN, - shopify_token:TEST_SHOPIFY_USER_TOKEN - )) - ShopifyApp::SessionStorage::UserStorageStrategy.const_set("User", mock_user_class) - - begin - ShopifyApp.configuration.per_user_tokens = true - session = MockSessionStore.retrieve(shopify_user_id:TEST_SHOPIFY_USER_ID) - - assert_equal TEST_SHOPIFY_DOMAIN, session.domain - assert_equal TEST_SHOPIFY_USER_TOKEN, session.token - ensure - ShopifyApp.configuration.per_user_tokens = false - ShopifyApp::SessionStorage::UserStorageStrategy.send(:remove_const , "User") - end - end - - test "tests that session store can store user session records" do - mock_user_instance = MockUserInstance.new(shopify_user_id:100) - mock_user_instance.stubs(:save!).returns(true) - - mock_user_class = Object.new - mock_user_class.stubs(:find_or_initialize_by).returns(mock_user_instance) - - ShopifyApp::SessionStorage::UserStorageStrategy.const_set("User", mock_user_class) - begin - ShopifyApp.configuration.per_user_tokens = true - - mock_auth_hash = mock() - mock_auth_hash.stubs(:domain).returns(mock_user_instance.shopify_domain) - mock_auth_hash.stubs(:token).returns("a-new-user_token!") - - associated_user = { - id: 100, - } - - saved_id = MockSessionStore.store(mock_auth_hash, user:associated_user) - - assert_equal "a-new-user_token!", mock_user_instance.shopify_token - assert_equal mock_user_instance.id, saved_id - - ensure - ShopifyApp.configuration.per_user_tokens = false - ShopifyApp::SessionStorage::UserStorageStrategy.send(:remove_const , "User") - end - end - end -end diff --git a/test/support/session_store_strategy_test_helpers.rb b/test/support/session_store_strategy_test_helpers.rb deleted file mode 100644 index 75a8101ef..000000000 --- a/test/support/session_store_strategy_test_helpers.rb +++ /dev/null @@ -1,32 +0,0 @@ -module SessionStoreStrategyTestHelpers - - class MockSessionStore < ActiveRecord::Base - include ShopifyApp::SessionStorage - end - - - class MockShopInstance - attr_reader :id, :shopify_domain, :shopify_token, :api_version - attr_writer :shopify_token - - def initialize(id:1, shopify_domain:'example.myshopify.com', shopify_token:'abcd-shop-token', api_version:'unstable') - @id = id - @shopify_domain = shopify_domain - @shopify_token = shopify_token - @api_version = api_version - end - end - - class MockUserInstance - attr_reader :id, :shopify_user_id, :shopify_domain, :shopify_token, :api_version - attr_writer :shopify_token, :shopify_domain - - def initialize(id:1, shopify_user_id:1, shopify_domain:'example.myshopify.com', shopify_token:'1234-user-token', api_version:'unstable') - @id = id - @shopify_user_id = shopify_user_id - @shopify_domain = shopify_domain - @shopify_token = shopify_token - @api_version = api_version - end - end -end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 1f909c1fa..187ba095a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -16,7 +16,6 @@ class ActiveSupport::TestCase include GeneratorTestHelpers - include SessionStoreStrategyTestHelpers API_META_TEST_RESPONSE = <<~JSON {