From 6253e80a708d9d45d2d38de6ed9b241a14d602a3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 20 Jan 2024 23:53:14 +0200 Subject: [PATCH] add testing environment --- Gemfile | 1 + Gemfile.lock | 3 ++ app/controllers/api/api_base_controller.rb | 2 ++ app/controllers/application_controller.rb | 13 ++++++- .../console_redirect_controller.rb | 4 +-- .../testing_accounts_controller.rb | 20 +++++++++++ .../testing_api_settings_controller.rb | 12 +++++++ app/models/account.rb | 14 ++++++++ app/models/account_linked_account.rb | 36 +++++++++++++++++++ app/views/accounts/show.html.erb | 2 +- app/views/api_settings/index.html.erb | 5 ++- app/views/icons/_api.html.erb | 7 ++++ app/views/icons/_code_circle.html.erb | 7 ++++ app/views/shared/_navbar_buttons.html.erb | 4 +++ app/views/shared/_settings_nav.html.erb | 22 +++++++++--- app/views/shared/_test_alert.html.erb | 17 +++++++++ app/views/shared/_test_mode_toggle.html.erb | 10 ++++++ app/views/testing_api_settings/index.html.erb | 18 ++++++++++ app/views/webhook_settings/show.html.erb | 5 ++- config/routes.rb | 8 ++--- ...20192055_create_account_linked_accounts.rb | 15 ++++++++ db/schema.rb | 15 +++++++- lib/accounts.rb | 23 +++++++++++- 23 files changed, 246 insertions(+), 17 deletions(-) create mode 100644 app/controllers/testing_accounts_controller.rb create mode 100644 app/controllers/testing_api_settings_controller.rb create mode 100644 app/models/account_linked_account.rb create mode 100644 app/views/icons/_api.html.erb create mode 100644 app/views/icons/_code_circle.html.erb create mode 100644 app/views/shared/_test_alert.html.erb create mode 100644 app/views/shared/_test_mode_toggle.html.erb create mode 100644 app/views/testing_api_settings/index.html.erb create mode 100644 db/migrate/20240120192055_create_account_linked_accounts.rb diff --git a/Gemfile b/Gemfile index 61eae029..d4983509 100644 --- a/Gemfile +++ b/Gemfile @@ -26,6 +26,7 @@ gem 'pagy' gem 'pdf-reader' gem 'pg', require: false gem 'premailer-rails' +gem 'pretender' gem 'puma' gem 'rack' gem 'rails' diff --git a/Gemfile.lock b/Gemfile.lock index 66882014..19cb2e0a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -354,6 +354,8 @@ GEM actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) + pretender (0.5.0) + actionpack (>= 6.1) pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) @@ -596,6 +598,7 @@ DEPENDENCIES pdf-reader pg premailer-rails + pretender pry-rails puma rack diff --git a/app/controllers/api/api_base_controller.rb b/app/controllers/api/api_base_controller.rb index 6232e3dc..60963b8d 100644 --- a/app/controllers/api/api_base_controller.rb +++ b/app/controllers/api/api_base_controller.rb @@ -8,6 +8,8 @@ module Api DEFAULT_LIMIT = 10 MAX_LIMIT = 100 + impersonates :user, with: ->(uuid) { User.find_by(uuid:) } + wrap_parameters false before_action :authenticate_user! diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 30e0a488..d7aa782a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -16,6 +16,8 @@ class ApplicationController < ActionController::Base :current_account, :svg_icon + impersonates :user, with: ->(uuid) { User.find_by(uuid:) } + rescue_from Pagy::OverflowError do redirect_to request.path end @@ -24,7 +26,7 @@ class ApplicationController < ActionController::Base rescue_from CanCan::AccessDenied do |e| Rollbar.error(e) if defined?(Rollbar) - redirect_back(fallback_location: root_path, alert: e.message) + redirect_to root_path, alert: e.message end end @@ -32,6 +34,15 @@ class ApplicationController < ActionController::Base Docuseal.default_url_options end + def impersonate_user(user) + raise ArgumentError unless user + raise Pretender::Error unless true_user + + @impersonated_user = user + + request.session[:impersonated_user_id] = user.uuid + end + private def with_browser_locale(&) diff --git a/app/controllers/console_redirect_controller.rb b/app/controllers/console_redirect_controller.rb index 5c4b037d..f6042225 100644 --- a/app/controllers/console_redirect_controller.rb +++ b/app/controllers/console_redirect_controller.rb @@ -5,9 +5,9 @@ class ConsoleRedirectController < ApplicationController skip_authorization_check def index - return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if current_user.blank? + return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if true_user.blank? - auth = JsonWebToken.encode(uuid: current_user.uuid, + auth = JsonWebToken.encode(uuid: true_user.uuid, scope: :console, exp: 1.minute.from_now.to_i) diff --git a/app/controllers/testing_accounts_controller.rb b/app/controllers/testing_accounts_controller.rb new file mode 100644 index 00000000..1bce910e --- /dev/null +++ b/app/controllers/testing_accounts_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class TestingAccountsController < ApplicationController + skip_authorization_check only: :destroy + + def show + authorize!(:manage, current_account) + authorize!(:manage, current_user) + + impersonate_user(Accounts.find_or_create_testing_user(current_account)) + + redirect_back(fallback_location: root_path) + end + + def destroy + stop_impersonating_user + + redirect_back(fallback_location: root_path) + end +end diff --git a/app/controllers/testing_api_settings_controller.rb b/app/controllers/testing_api_settings_controller.rb new file mode 100644 index 00000000..5eade647 --- /dev/null +++ b/app/controllers/testing_api_settings_controller.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class TestingApiSettingsController < ApplicationController + def index + authorize!(:manage, current_user.access_token) + + @webhook_config = + current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY) + + authorize!(:manage, @webhook_config) + end +end diff --git a/app/models/account.rb b/app/models/account.rb index 8c2792e9..2689bd1c 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -22,12 +22,26 @@ class Account < ApplicationRecord class_name: 'TemplateFolder', dependent: :destroy, inverse_of: :account has_many :submissions, through: :templates has_many :submitters, through: :submissions + has_many :account_linked_accounts, dependent: :destroy + has_many :account_testing_accounts, -> { testing }, dependent: :destroy, + class_name: 'AccountLinkedAccount', + inverse_of: :account + has_one :linked_account_account, dependent: :destroy, + foreign_key: :linked_account_id, + class_name: 'AccountLinkedAccount', + inverse_of: :linked_account + has_many :linked_accounts, through: :account_linked_accounts + has_many :testing_accounts, through: :account_testing_accounts, source: :linked_account has_many :active_users, -> { active }, dependent: :destroy, inverse_of: :account, class_name: 'User' attribute :timezone, :string, default: 'UTC' attribute :locale, :string, default: 'en-US' + def testing? + linked_account_account&.testing? + end + def default_template_folder super || build_default_template_folder(name: TemplateFolder::DEFAULT_NAME, author_id: users.minimum(:id)).tap(&:save!) diff --git a/app/models/account_linked_account.rb b/app/models/account_linked_account.rb new file mode 100644 index 00000000..3d68aea7 --- /dev/null +++ b/app/models/account_linked_account.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: account_linked_accounts +# +# id :bigint not null, primary key +# account_type :text not null +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint not null +# linked_account_id :bigint not null +# +# Indexes +# +# idx_on_account_id_linked_account_id_48ab9f79d2 (account_id,linked_account_id) UNIQUE +# index_account_linked_accounts_on_account_id (account_id) +# index_account_linked_accounts_on_linked_account_id (linked_account_id) +# +# Foreign Keys +# +# fk_rails_... (account_id => accounts.id) +# fk_rails_... (linked_account_id => accounts.id) +# +class AccountLinkedAccount < ApplicationRecord + belongs_to :account + belongs_to :linked_account, class_name: 'Account' + + attribute :account_type, :string, default: 'testing' + + scope :testing, -> { where(account_type: :testing) } + + def testing? + account_type == 'testing' + end +end diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index 7e570f67..06c86853 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -22,7 +22,7 @@ <% end %> <% encrypted_config = @encrypted_config || EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY) %> - <% if !Docuseal.multitenant? && can?(:manage, encrypted_config) %> + <% if !Docuseal.multitenant? && can?(:manage, encrypted_config) && !current_account.testing? %> <%= f.fields_for encrypted_config do |ff| %>
<%= ff.label :value, 'App URL', class: 'label' %> diff --git a/app/views/api_settings/index.html.erb b/app/views/api_settings/index.html.erb index cae10942..e91bbe47 100644 --- a/app/views/api_settings/index.html.erb +++ b/app/views/api_settings/index.html.erb @@ -1,7 +1,10 @@
<%= render 'shared/settings_nav' %>
-

API

+
+

API

+ <%= render 'shared/test_mode_toggle' %> +
diff --git a/app/views/icons/_api.html.erb b/app/views/icons/_api.html.erb new file mode 100644 index 00000000..2d83a288 --- /dev/null +++ b/app/views/icons/_api.html.erb @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/views/icons/_code_circle.html.erb b/app/views/icons/_code_circle.html.erb new file mode 100644 index 00000000..5a55acba --- /dev/null +++ b/app/views/icons/_code_circle.html.erb @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/views/shared/_navbar_buttons.html.erb b/app/views/shared/_navbar_buttons.html.erb index a0a246de..7670205f 100644 --- a/app/views/shared/_navbar_buttons.html.erb +++ b/app/views/shared/_navbar_buttons.html.erb @@ -1,4 +1,8 @@ <%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %> Upgrade <% end %> +<% if signed_in? && current_user != true_user %> + + <%= render 'shared/test_alert' %> +<% end %> diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb index 9ca17b66..12da5ce1 100644 --- a/app/views/shared/_settings_nav.html.erb +++ b/app/views/shared/_settings_nav.html.erb @@ -11,17 +11,17 @@ <%= link_to 'Account', settings_account_path, class: 'text-base hover:bg-base-300' %> <% unless Docuseal.multitenant? %> - <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? %> + <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %>
  • <%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> - <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) %> + <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user %>
  • <%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> - <% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) %> + <% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user %>
  • <%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
  • @@ -43,7 +43,7 @@ <% end %> <% unless Docuseal.multitenant? %> - <% if can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) %> + <% if can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user %>
  • <%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
  • @@ -88,10 +88,22 @@ <% end %> <% end %> + <% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %> +
  • + <%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex w-full' } do |f| %> + + <% end %> +
  • + <% end %> <% end %> - <% if !can?(:manage, :tenants) %> + <% if Docuseal.multitenant? || cannot?(:manage, :tenants) %>