├── .circleci └── config.yml ├── .github └── CODEOWNERS ├── .gitignore ├── .overcommit.yml ├── .rspec ├── .rubocop.yml ├── .ruby-gemset ├── .ruby-version ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── omni_auth │ ├── multi_provider.rb │ └── multi_provider │ │ ├── handler.rb │ │ └── version.rb └── omniauth-multi-provider.rb ├── omniauth-multi-provider.gemspec └── spec ├── omni_auth ├── multi_provider │ └── handler_spec.rb └── multi_provider_spec.rb └── spec_helper.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | lint: 4 | docker: 5 | - image: cimg/ruby:2.7.7 6 | working_directory: ~/omniauth-multi-provider 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | keys: 11 | - v1-gems-ruby-2.7.7-{{ checksum "omniauth-multi-provider.gemspec" }}-{{ checksum "Gemfile" }} 12 | - v1-gems-ruby-2.7.7- 13 | - run: 14 | name: Install Gems 15 | command: | 16 | if ! bundle check --path=vendor/bundle; then 17 | bundle install --path=vendor/bundle --jobs=4 --retry=3 18 | bundle clean 19 | fi 20 | - save_cache: 21 | key: v1-gems-ruby-2.7.7-{{ checksum "omniauth-multi-provider.gemspec" }}-{{ checksum "Gemfile" }} 22 | paths: 23 | - "vendor/bundle" 24 | - "gemfiles/vendor/bundle" 25 | - run: 26 | name: Run Rubocop 27 | command: bundle exec rubocop --config .rubocop.yml 28 | test: 29 | parameters: 30 | ruby_version: 31 | type: string 32 | docker: 33 | - image: cimg/ruby:<< parameters.ruby_version >> 34 | environment: 35 | CIRCLE_TEST_REPORTS: "test-results" 36 | working_directory: ~/omniauth-multi-provider 37 | steps: 38 | - checkout 39 | - restore_cache: 40 | keys: 41 | - v1-gems-ruby-<< parameters.ruby_version >>-{{ checksum "omniauth-multi-provider.gemspec" }}-{{ checksum "Gemfile" }} 42 | - v1-gems-ruby-<< parameters.ruby_version >>- 43 | - run: 44 | name: Install Gems 45 | command: | 46 | if ! bundle check --path=vendor/bundle; then 47 | bundle install --path=vendor/bundle --jobs=4 --retry=3 48 | bundle clean 49 | fi 50 | - save_cache: 51 | key: v1-gems-ruby-<< parameters.ruby_version >>-{{ checksum "omniauth-multi-provider.gemspec" }}-{{ checksum "Gemfile" }} 52 | paths: 53 | - "vendor/bundle" 54 | - "gemfiles/vendor/bundle" 55 | - run: 56 | name: Run Tests 57 | command: | 58 | bundle exec rspec --format RspecJunitFormatter --out $CIRCLE_TEST_REPORTS/rspec/junit.xml --format progress spec 59 | - store_test_results: 60 | path: "test-results" 61 | workflows: 62 | build: 63 | jobs: 64 | - lint 65 | - test: 66 | matrix: 67 | parameters: 68 | ruby_version: 69 | - 2.7.7 70 | - 3.0.5 71 | - 3.1.3 72 | - 3.2.0 73 | - 3.3.0 74 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @salsify/pim-core-backend 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | PreCommit: 2 | RuboCop: 3 | enabled: true 4 | required: false 5 | on_warn: fail 6 | 7 | HardTabs: 8 | enabled: true 9 | required: false 10 | 11 | CommitMsg: 12 | TrailingPeriod: 13 | enabled: false 14 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | salsify_rubocop: conf/rubocop.yml 3 | 4 | AllCops: 5 | TargetRubyVersion: 2.7 6 | Exclude: 7 | - 'vendor/**/*' 8 | - 'gemfiles/**/*' 9 | 10 | Naming/FileName: 11 | Exclude: 12 | - 'lib/omniauth-multi-provider.rb' 13 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | omniauth-multi-provider 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.6.6 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # omniauth-multi-provider 2 | 3 | ## v0.4.0 4 | - Drop support for Ruby 2.6 5 | - Add support for Ruby 3.1 and 3.2 6 | 7 | ## v0.3.0 8 | - Drop support for Ruby < 2.6. 9 | - Add support for Ruby 3.0. 10 | 11 | ## v0.2.1 12 | - Update gemspec for minimum Ruby version of 2.1. 13 | 14 | ## v0.2.0 15 | - Support configuration of the callback suffix. Thanks [Martin Holman](https://github.com/martin308). 16 | 17 | ## v0.1.0 18 | - Initial version 19 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | 6 | # override the :github shortcut to be secure by using HTTPS 7 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" } 8 | 9 | # Specify your gem's dependencies in omniauth-multi-provider.gemspec 10 | gemspec 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Salsify, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OmniAuth MultiProvider 2 | 3 | This is a simple extension to [omniauth](https://github.com/omniauth/omniauth) for supporting 4 | multiple identity provider instances of a given type e.g. multiple SAML or OAuth2 5 | identity providers. It is a generalization of the 6 | [omniauth-multi-provider-saml](https://github.com/salsify/omniauth-multi-provider-saml). 7 | 8 | ## Installation 9 | 10 | Add this line to your application's Gemfile: 11 | 12 | ```ruby 13 | gem 'omniauth-multi-provider' 14 | ``` 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | Or install it yourself as: 21 | 22 | $ gem install omniauth-multi-provider 23 | 24 | ## Setup 25 | 26 | **Getting your setup to work with a single identity provider before attempting to use this gem is highly recommended.** 27 | 28 | The setup process consists of the following steps: 29 | 30 | 1. Create an OmniAuth callback controller for your identity provider like you normally would with OmniAuth. 31 | 1. Configure your routes to handle routes for multiple identity provider instances. 32 | 1. Configure omniauth-multi-provider to choose the appropriate identity provider instance. 33 | 34 | ### Configure Routes 35 | 36 | Add something like the following to your routes assuming you're using Rails and a SAML identity provider 37 | (your actual URL structure may vary): 38 | 39 | ```ruby 40 | MyApplication::Application.routes.draw do 41 | match '/auth/saml/:identity_provider_id/callback', 42 | via: [:get, :post], 43 | to: 'omniauth_callbacks#saml', 44 | as: 'user_omniauth_callback' 45 | 46 | match '/auth/saml/:identity_provider_id', 47 | via: [:get, :post], 48 | to: 'omniauth_callbacks#passthru', 49 | as: 'user_omniauth_authorize' 50 | end 51 | ``` 52 | 53 | ### Configure OmniAuth 54 | 55 | The basic configuration of OmniAuth looks something like this: 56 | 57 | ```ruby 58 | # config/initializers/omniauth.rb 59 | Rails.application.config.middleware.use OmniAuth::Builder do 60 | OmniAuth::MultiProvider.register(self, 61 | provider_name: :saml, 62 | identity_provider_id_regex: /\d+/, 63 | path_prefix: '/auth/saml', 64 | callback_suffix: 'callback', 65 | # Specify any additional provider specific options 66 | name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', 67 | issuer: 'salsify.com', 68 | allowed_clock_drift: 5.seconds) do |identity_provider_id, rack_env| 69 | identity_provider = SAML::IdentityProvider.find(identity_provider_id) 70 | # Optionally store a reference to the identity provider in the Rack environment 71 | # so you can reference it in your OmniAuth callbacks controller 72 | rack_env['salsify.saml_identity_provider'] = identity_provider 73 | # Any dynamic options returned by this block will be merged in with any statically 74 | # configured options for the identity provider type e.g. issuer in this example. 75 | identity_provider.options 76 | end 77 | 78 | # This also works with multiple provider types 79 | OmniAuth::MultiProvider.register(self, 80 | provider_name: :oauth2, 81 | identity_provider_id_regex: /\d+/, 82 | path_prefix: '/auth/oauth2') do |identity_provider_id, rack_env| 83 | identity_provider = OAuth2::IdentityProvider.find(identity_provider_id) 84 | rack_env['salsify.oauth2_identity_provider'] = identity_provider 85 | identity_provider.options 86 | end 87 | end 88 | ``` 89 | 90 | ## Development 91 | 92 | After checking out the repo, run `bin/setup` to install dependencies. Then, 93 | run `rake spec` to run the tests. You can also run `bin/console` for an 94 | interactive prompt that will allow you to experiment. 95 | 96 | To install this gem onto your local machine, run `bundle exec rake install`. 97 | 98 | To release a new version, update the version number in `version.rb`, and then 99 | run `bundle exec rake release`, which will create a git tag for the version, 100 | push git commits and tags, and push the `.gem` file to 101 | [rubygems.org](https://rubygems.org) 102 | . 103 | 104 | ## Contributing 105 | 106 | Bug reports and pull requests are welcome on GitHub at 107 | https://github.com/salsify/omniauth-multi-provider.## License 108 | 109 | The gem is available as open source under the terms of the 110 | [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | 5 | 6 | require 'rspec/core/rake_task' 7 | 8 | RSpec::Core::RakeTask.new(:spec) 9 | 10 | task default: :spec 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | require 'omniauth-multi-provider' 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require 'irb' 15 | IRB.start 16 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -v 5 | 6 | bundle update 7 | 8 | overcommit --install 9 | -------------------------------------------------------------------------------- /lib/omni_auth/multi_provider.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'omniauth' 4 | 5 | require 'omni_auth/multi_provider/handler' 6 | require 'omni_auth/multi_provider/version' 7 | 8 | module OmniAuth 9 | module MultiProvider 10 | def self.register(builder, 11 | provider_name:, 12 | path_prefix: ::OmniAuth.config.path_prefix, 13 | **options, &dynamic_options_generator) 14 | 15 | handler = OmniAuth::MultiProvider::Handler.new(path_prefix: path_prefix, 16 | **options, 17 | &dynamic_options_generator) 18 | 19 | static_options = options.merge(path_prefix: path_prefix) 20 | 21 | builder.provider(provider_name, static_options.merge(handler.provider_options)) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/omni_auth/multi_provider/handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OmniAuth 4 | module MultiProvider 5 | class Handler 6 | attr_reader :path_prefix, :provider_instance_path_regex, :request_path_regex, 7 | :callback_path_regex, :callback_suffix, 8 | :identity_provider_options_generator 9 | 10 | def initialize(path_prefix:, 11 | identity_provider_id_regex:, 12 | callback_suffix: 'callback', 13 | **_options, 14 | &identity_provider_options_generator) 15 | raise 'Missing provider options generator block' unless block_given? 16 | 17 | @path_prefix = path_prefix 18 | @identity_provider_options_generator = identity_provider_options_generator 19 | @identity_provider_id_regex = identity_provider_id_regex 20 | @callback_suffix = callback_suffix 21 | 22 | # Eagerly compute these since lazy evaluation will not be threadsafe 23 | @provider_instance_path_regex = /^#{@path_prefix}\/(?#{@identity_provider_id_regex})/ 24 | @request_path_regex = /#{@provider_instance_path_regex}\/?$/ 25 | @callback_path_regex = /#{@provider_instance_path_regex}\/#{@callback_suffix}\/?$/ 26 | end 27 | 28 | def provider_options 29 | { 30 | request_path: method(:request_path?), 31 | callback_path: method(:callback_path?), 32 | setup: method(:setup) 33 | } 34 | end 35 | 36 | def request_path?(env) 37 | path = current_path(env) 38 | !!request_path_regex.match(path) 39 | end 40 | 41 | def callback_path?(env) 42 | path = current_path(env) 43 | !!callback_path_regex.match(path) 44 | end 45 | 46 | def setup(env) 47 | identity_provider_id = extract_identity_provider_id(env) 48 | if identity_provider_id 49 | strategy = env['omniauth.strategy'] 50 | add_path_options(strategy, identity_provider_id) 51 | add_identity_provider_options(strategy, env, identity_provider_id) 52 | end 53 | end 54 | 55 | private 56 | 57 | def add_path_options(strategy, identity_provider_id) 58 | strategy.options.merge!( 59 | request_path: "#{path_prefix}/#{identity_provider_id}", 60 | callback_path: "#{path_prefix}/#{identity_provider_id}/#{callback_suffix}" 61 | ) 62 | end 63 | 64 | def add_identity_provider_options(strategy, env, identity_provider_id) 65 | identity_provider_options = identity_provider_options_generator.call(identity_provider_id, env) || {} 66 | strategy.options.merge!(identity_provider_options) 67 | rescue StandardError => e 68 | result = strategy.fail!(:invalid_identity_provider, e) 69 | throw :warden, result 70 | end 71 | 72 | def current_path(env) 73 | env['PATH_INFO'] 74 | end 75 | 76 | def extract_identity_provider_id(env) 77 | path = current_path(env) 78 | match = provider_instance_path_regex.match(path) 79 | match ? match[:identity_provider_id] : nil 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/omni_auth/multi_provider/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OmniAuth 4 | module MultiProvider 5 | VERSION = '0.4.0' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/omniauth-multi-provider.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'omni_auth/multi_provider' 4 | -------------------------------------------------------------------------------- /omniauth-multi-provider.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'omni_auth/multi_provider/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'omniauth-multi-provider' 9 | spec.version = OmniAuth::MultiProvider::VERSION 10 | spec.authors = ['Salsify, Inc'] 11 | spec.email = ['engineering@salsify.com'] 12 | 13 | spec.summary = 'OmniAuth support for multiple providers of an authentication strategy' 14 | spec.description = spec.summary 15 | spec.homepage = 'https://github.com/salsify/omniauth-multi-provider' 16 | 17 | spec.license = 'MIT' 18 | 19 | # Set 'allowed_push_post' to control where this gem can be published. 20 | if spec.respond_to?(:metadata) 21 | spec.metadata['allowed_push_host'] = 'https://rubygems.org' 22 | spec.metadata['rubygems_mfa_required'] = 'true' 23 | else 24 | raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' 25 | end 26 | 27 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 28 | spec.bindir = 'bin' 29 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 30 | spec.require_paths = ['lib'] 31 | 32 | spec.required_ruby_version = '>= 2.7' 33 | 34 | spec.add_dependency 'omniauth' 35 | 36 | spec.add_development_dependency 'bundler', '~> 2.0' 37 | spec.add_development_dependency 'overcommit' 38 | spec.add_development_dependency 'rake', '~> 13.0' 39 | spec.add_development_dependency 'rspec', '~> 3.8' 40 | spec.add_development_dependency 'rspec_junit_formatter' 41 | spec.add_development_dependency 'salsify_rubocop', '~> 1.43.0' 42 | end 43 | -------------------------------------------------------------------------------- /spec/omni_auth/multi_provider/handler_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe OmniAuth::MultiProvider::Handler do 4 | let(:dynamic_provider_options) do 5 | { 6 | foo: 'bar' 7 | } 8 | end 9 | 10 | let(:provider_options_generator) do 11 | Proc.new { dynamic_provider_options } 12 | end 13 | 14 | let(:path_prefix) { '/auth/saml' } 15 | let(:handler) do 16 | described_class.new( 17 | path_prefix: path_prefix, 18 | identity_provider_id_regex: /\d+/, 19 | **handler_options, 20 | &provider_options_generator 21 | ) 22 | end 23 | 24 | let(:handler_options) do 25 | {} 26 | end 27 | 28 | let(:strategy) do 29 | instance_double(OmniAuth::Strategy, options: {}) 30 | end 31 | 32 | let(:provider_id) { 12345 } 33 | let(:path) { "#{path_prefix}/#{provider_id}" } 34 | 35 | let(:env) do 36 | { 37 | 'omniauth.strategy' => strategy, 38 | 'PATH_INFO' => path 39 | } 40 | end 41 | 42 | describe "#provider_options" do 43 | it "returns a hash with setup, request_path, callback_path" do 44 | expect(handler.provider_options).to eq(setup: handler.method(:setup), 45 | request_path: handler.method(:request_path?), 46 | callback_path: handler.method(:callback_path?)) 47 | end 48 | end 49 | 50 | describe "#setup" do 51 | shared_examples_for "it does not set any strategy options" do 52 | specify do 53 | expect(strategy.options.keys).to contain_exactly(:request_path, :callback_path) 54 | end 55 | end 56 | 57 | context "when the request path has a valid provider id" do 58 | before do 59 | handler.setup(env) 60 | end 61 | 62 | it "sets the strategy's request path" do 63 | expect(strategy.options[:request_path]).to eq("#{path_prefix}/#{provider_id}") 64 | end 65 | 66 | it "sets the strategy's callback path" do 67 | expect(strategy.options[:callback_path]).to eq("#{path_prefix}/#{provider_id}/callback") 68 | end 69 | 70 | it "adds the options returned by the identity_provider_options_generator to the strategy's options" do 71 | expect(strategy.options).to include(dynamic_provider_options) 72 | end 73 | end 74 | 75 | context "when the request path does not match a valid provider id" do 76 | let(:provider_id) { 'invalid' } 77 | 78 | before do 79 | handler.setup(env) 80 | end 81 | 82 | it "does not set any strategy options" do 83 | expect(strategy.options).to be_empty 84 | end 85 | end 86 | 87 | context "when the identity_provider_options_generator returns nil" do 88 | let(:dynamic_provider_options) {} 89 | 90 | before do 91 | handler.setup(env) 92 | end 93 | 94 | it "only sets the request path and callback path strategy options" do 95 | expect(strategy.options.keys).to contain_exactly(:request_path, :callback_path) 96 | end 97 | end 98 | 99 | context "when the identity_provider_options_generator raises an exception" do 100 | let(:exception) { StandardError.new('test exception') } 101 | let(:failure_result) { double } 102 | 103 | let(:provider_options_generator) do 104 | Proc.new { raise exception } 105 | end 106 | 107 | before do 108 | allow(strategy).to receive(:fail!).and_return(failure_result) 109 | end 110 | 111 | it "throws a warden symbol" do 112 | expect { handler.setup(env) }.to throw_symbol(:warden, failure_result) 113 | end 114 | 115 | it "calls Strategy#fail! with the appropriate arguments" do 116 | catch(:warden) { handler.setup(env) } 117 | expect(strategy).to have_received(:fail!).with(:invalid_identity_provider, exception) 118 | end 119 | end 120 | end 121 | 122 | describe "#request_path?" do 123 | context "when the path is a request path" do 124 | let(:path) { "#{path_prefix}/#{provider_id}" } 125 | 126 | it "returns true" do 127 | expect(handler.request_path?(env)).to be(true) 128 | end 129 | end 130 | 131 | context "when the path is a request path with a trailing segment" do 132 | let(:path) { "#{path_prefix}/#{provider_id}/foo" } 133 | 134 | it "returns false" do 135 | expect(handler.request_path?(env)).to be(false) 136 | end 137 | end 138 | 139 | context "when the path is a request path with a leading segment" do 140 | let(:path) { "/foo#{path_prefix}/#{provider_id}" } 141 | 142 | it "returns false" do 143 | expect(handler.request_path?(env)).to be(false) 144 | end 145 | end 146 | 147 | context "when the path is a request path with an invalid provider id" do 148 | let(:path) { "#{path_prefix}/foobar" } 149 | 150 | it "returns false" do 151 | expect(handler.request_path?(env)).to be(false) 152 | end 153 | end 154 | end 155 | 156 | describe "#callback_path?" do 157 | 158 | describe "custom callback_path" do 159 | let(:handler_options) do 160 | { 161 | callback_suffix: 'wow' 162 | } 163 | end 164 | 165 | context "when the path is a callback path" do 166 | let(:path) { "#{path_prefix}/#{provider_id}/wow" } 167 | 168 | it "returns true" do 169 | expect(handler.callback_path?(env)).to be(true) 170 | end 171 | end 172 | 173 | context "when the path is a request path" do 174 | let(:path) { "#{path_prefix}/#{provider_id}" } 175 | 176 | it "returns false" do 177 | expect(handler.callback_path?(env)).to be(false) 178 | end 179 | end 180 | 181 | context "when the path is a callback path with a trailing segment" do 182 | let(:path) { "#{path_prefix}/#{provider_id}/wow/foo" } 183 | 184 | it "returns false" do 185 | expect(handler.callback_path?(env)).to be(false) 186 | end 187 | end 188 | 189 | context "when the path is a callback path with a leading segment" do 190 | let(:path) { "/foo#{path_prefix}/#{provider_id}/wow" } 191 | 192 | it "returns false" do 193 | expect(handler.callback_path?(env)).to be(false) 194 | end 195 | end 196 | 197 | context "when the path is a callback path with an invalid provider id" do 198 | let(:path) { "#{path_prefix}/foobar/wow" } 199 | 200 | it "returns false" do 201 | expect(handler.callback_path?(env)).to be(false) 202 | end 203 | end 204 | end 205 | 206 | describe "default callback_path" do 207 | context "when the path is a callback path" do 208 | let(:path) { "#{path_prefix}/#{provider_id}/callback" } 209 | 210 | it "returns true" do 211 | expect(handler.callback_path?(env)).to be(true) 212 | end 213 | end 214 | 215 | context "when the path is a request path" do 216 | let(:path) { "#{path_prefix}/#{provider_id}" } 217 | 218 | it "returns false" do 219 | expect(handler.callback_path?(env)).to be(false) 220 | end 221 | end 222 | 223 | context "when the path is a callback path with a trailing segment" do 224 | let(:path) { "#{path_prefix}/#{provider_id}/callback/foo" } 225 | 226 | it "returns false" do 227 | expect(handler.callback_path?(env)).to be(false) 228 | end 229 | end 230 | 231 | context "when the path is a callback path with a leading segment" do 232 | let(:path) { "/foo#{path_prefix}/#{provider_id}/callback" } 233 | 234 | it "returns false" do 235 | expect(handler.callback_path?(env)).to be(false) 236 | end 237 | end 238 | 239 | context "when the path is a callback path with an invalid provider id" do 240 | let(:path) { "#{path_prefix}/foobar/callback" } 241 | 242 | it "returns false" do 243 | expect(handler.callback_path?(env)).to be(false) 244 | end 245 | end 246 | end 247 | end 248 | end 249 | -------------------------------------------------------------------------------- /spec/omni_auth/multi_provider_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe OmniAuth::MultiProvider do 4 | describe ".register" do 5 | let(:builder) { instance_double(OmniAuth::Builder, provider: nil) } 6 | let(:provider_name) { :my_provider } 7 | let(:path_prefix) { '/auth' } 8 | let(:identity_provider_id_regex) { /\d+/ } 9 | let(:static_provider_options) do 10 | { 11 | foo: 'bar' 12 | } 13 | end 14 | 15 | before do 16 | described_class.register(builder, provider_name: provider_name, path_prefix: path_prefix, 17 | identity_provider_id_regex: identity_provider_id_regex, **static_provider_options) do 18 | nil 19 | end 20 | end 21 | 22 | it "registers the provider with static provider options" do 23 | expect(builder).to have_received(:provider).with(provider_name, hash_including(static_provider_options)) 24 | end 25 | 26 | it "registers the provider with setup, request_path, and callback_path options" do 27 | expect(builder).to have_received(:provider).with(provider_name, 28 | hash_including(:setup, :request_path, :callback_path)) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 4 | require 'omniauth-multi-provider' 5 | --------------------------------------------------------------------------------