├── .gitignore
├── CHANGELOG.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app
├── controllers
│ └── rails_sso
│ │ └── sessions_controller.rb
└── services
│ └── rails_sso
│ └── fetch_user.rb
├── circle.yml
├── config
├── locales
│ └── en.yml
└── routes.rb
├── lib
├── generators
│ ├── rails_sso_generator.rb
│ └── templates
│ │ └── sso.rb
├── rails_sso.rb
├── rails_sso
│ ├── app.rb
│ ├── client.rb
│ ├── configuration.rb
│ ├── engine.rb
│ ├── failure_app.rb
│ ├── helpers.rb
│ ├── response_error.rb
│ ├── sso_strategy.rb
│ ├── token_mock.rb
│ └── version.rb
└── tasks
│ └── rails_sso_tasks.rake
├── rails_sso.gemspec
└── test
├── controllers
└── rails_sso
│ └── sessions_controller_test.rb
├── dummy
├── README.rdoc
├── Rakefile
├── app
│ ├── assets
│ │ ├── images
│ │ │ └── .keep
│ │ ├── javascripts
│ │ │ └── application.js
│ │ └── stylesheets
│ │ │ └── application.css
│ ├── controllers
│ │ ├── application_controller.rb
│ │ └── concerns
│ │ │ └── .keep
│ ├── helpers
│ │ └── application_helper.rb
│ ├── mailers
│ │ └── .keep
│ ├── models
│ │ ├── .keep
│ │ └── concerns
│ │ │ └── .keep
│ └── views
│ │ └── layouts
│ │ └── application.html.erb
├── bin
│ ├── bundle
│ ├── rails
│ ├── rake
│ └── setup
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── session_store.rb
│ │ ├── sso.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── routes.rb
│ └── secrets.yml
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
└── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ └── favicon.ico
├── lib
└── rails_sso
│ ├── app_test.rb
│ ├── configuration_test.rb
│ ├── failure_app_test.rb
│ ├── helpers_test.rb
│ ├── response_error_test.rb
│ └── sso_strategy_test.rb
├── rails_sso_test.rb
├── routes
└── sso_routes_test.rb
├── services
└── rails_sso
│ └── fetch_user_test.rb
└── test_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 | test/dummy/db/*.sqlite3
5 | test/dummy/db/*.sqlite3-journal
6 | test/dummy/log/*.log
7 | test/dummy/tmp/
8 | test/dummy/.sass-cache
9 | Gemfile.lock
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v0.7.5 - 17.03.2016
4 |
5 | * Use `request.xhr?` instead of `request.content_type` in failure app
6 |
7 | ## v0.7.3 - 09.12.2015
8 |
9 | * Fix user_signed_in? on not restricted actions
10 |
11 | ## v0.7.2 - 08.12.2015
12 |
13 | * Fix fetching user data on not restricted actions
14 |
15 | ## v0.7.1 - 11.09.2015
16 |
17 | * Wrap configuration logic into Configuration class
18 | * Raise error when provider_url is missing
19 | * Fix mocking token
20 |
21 | ## v0.7.0 - 19.08.2015
22 |
23 | * Remember user's current path
24 | * Allow to mock signed out state
25 |
26 | **Backward incomatible changes**
27 |
28 | * Select profile mock based on access token mock
29 | * Require explicite include of helpers
30 |
31 | ## v0.6.0 - 29.06.2015
32 |
33 | * Mock profile request, not OmniAuth
34 | * Custom FailureApp
35 | * Handle JSON in default FailureApp
36 |
37 | ## v0.5.0 - 29.04.2015
38 |
39 | * Mocking feature
40 |
41 | ## v0.4.0 - 09.04.2015
42 |
43 | * Use warden under the hood
44 | * Inject into initialization process so calling for OmniAuth client could take Rack env
45 |
46 | ## v0.3.5 - 01.04.2015
47 |
48 | * Use Rails rack app when initializing omniauth strategy object
49 |
50 | ## v0.3.4 - 01.04.2015
51 |
52 | * Support OmniAuth camelization strategy
53 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Declare your gem's dependencies in rails_sso.gemspec.
4 | # Bundler will treat runtime dependencies like base dependencies, and
5 | # development dependencies will be added by default to the :development group.
6 | gemspec
7 |
8 | group :test do
9 | gem 'codeclimate-test-reporter', require: nil
10 | end
11 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 Jan Dudulski
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SSO client Rails Engine
2 |
3 | [](https://gitter.im/monterail/rails_sso?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |
5 | [](https://circleci.com/gh/monterail/rails_sso/tree/master)
6 | [](https://gemnasium.com/monterail/rails_sso)
7 | [](http://badge.fury.io/rb/rails_sso)
8 | [](https://codeclimate.com/github/monterail/rails_sso)
9 | [](https://codeclimate.com/github/monterail/rails_sso)
10 |
11 | ## About
12 |
13 | *SOON*
14 |
15 | ## Installation
16 |
17 | Add engine and [omniauth](https://github.com/intridea/omniauth-oauth2) provider gems to your project:
18 |
19 | ```ruby
20 | # Gemfile
21 |
22 | gem 'omniauth-example'
23 | gem 'rails_sso'
24 | ```
25 |
26 | Install initializer and mount routes:
27 |
28 | ```bash
29 | bin/rails generate rails_sso
30 | ```
31 |
32 | Configure initializer:
33 |
34 | ```ruby
35 | # conifg/initializers/sso.rb
36 |
37 | RailsSso.configure do |config|
38 | # include RailsSso::Helpers to ActionController::Base
39 | config.magic_enabled = true
40 | # url of entity provider
41 | config.provider_url = 'https://example.com'
42 | # name of oauth2 provider
43 | config.provider_name = 'example'
44 | # oauth keys for omniauth-example
45 | config.provider_key = ENV['PROVIDER_KEY']
46 | config.provider_secret = ENV['PROVIDER_SECRET']
47 | # path for fetching user data
48 | config.provider_profile_path = '/api/v1/profile'
49 | # set if you support single sign out
50 | config.provider_sign_out_path = '/api/v1/session'
51 | # enable cache (will use Rails.cache store)
52 | config.use_cache = Rails.application.config.action_controller.perform_caching
53 | # test & development mode
54 | config.test_mode = ENV['mock_sso']
55 | config.access_token_mock = ENV['access_token_mock']
56 | config.profile_mocks = {
57 | '169783' => {
58 | user: 'John Blacksmith',
59 | uid: '169783'
60 | }
61 | }
62 | # custom failure app
63 | # more: https://github.com/hassox/warden/wiki/Failures
64 | config.failure_app = MyFailureApp
65 | end
66 | ```
67 |
68 | And mount it:
69 |
70 | ```ruby
71 | # config/routes.rb
72 |
73 | Rails.application.routes.draw do
74 | mount RailsSso::Engine => '/sso', as: 'sso'
75 | end
76 | ```
77 |
78 | ## Usage
79 |
80 | Include helpers to your controller if you disabled auto include:
81 |
82 | ```ruby
83 | class ApplicationController < ActionController::Base
84 | include RailsSso::Helpers
85 | end
86 | ```
87 |
88 | Available helpers for controllers and views:
89 |
90 | * `current_user_data`
91 | * `user_signed_in?`
92 |
93 | Available filters and helpers for controllers:
94 |
95 | * `authenticate_user!`
96 | * `sign_in_with_access_token!(access_token)`
97 | * `sign_out!`
98 | * `warden`
99 | * `sso_app`
100 |
101 | Available helpers for views:
102 |
103 | * `sso.sign_in_path`
104 | * `sso.sign_out_path`
105 |
106 | ## Testing & Development mode
107 |
108 | You can turn on "test mode" by enabling test mode. It will also automatically enable [OmniAuth test mode](https://github.com/intridea/omniauth/wiki/Integration-Testing).
109 |
110 | ```ruby
111 | RailsSso.configure do
112 | config.test_mode = true
113 | end
114 | ```
115 |
116 | Mock data should be passed to `profile_mocks` configuration with dummy access token as a key.
117 |
118 | ```ruby
119 | RailsSso.configure do |config|
120 | config.profile_mocks = {
121 | "kowalski_uid" => {
122 | "name" => "John Kowalski",
123 | "uid" => "42"
124 | },
125 | "nowak_uid" => {
126 | "name" => "Pawel Nowak",
127 | "uid" => "23"
128 | }
129 | }
130 | end
131 | ```
132 |
133 | Finally you have to select which token to use.
134 |
135 | ```ruby
136 | RailsSso.configure do |config|
137 | config.access_token_mock = "kowalski_uid"
138 | end
139 | ```
140 |
141 | To mock signed out state set `nil` to access token value.
142 |
143 | ## Contributing
144 |
145 | 1. Fork it
146 | 2. Create your feature branch (`git checkout -b my-new-feature`)
147 | 3. Commit your changes (`git commit -am 'Add some feature'`)
148 | 4. Push to the branch (`git push origin my-new-feature`)
149 | 5. Create new Pull Request
150 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | begin
2 | require 'bundler/setup'
3 | rescue LoadError
4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5 | end
6 |
7 | Bundler::GemHelper.install_tasks
8 |
9 | require 'rake/testtask'
10 |
11 | Rake::TestTask.new(:test) do |t|
12 | t.libs << 'lib'
13 | t.libs << 'test'
14 | t.pattern = 'test/**/*_test.rb'
15 | t.verbose = false
16 | end
17 |
18 |
19 | task default: :test
20 |
--------------------------------------------------------------------------------
/app/controllers/rails_sso/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | class SessionsController < RailsSso.config.application_controller.constantize
3 | skip_before_action :authenticate_user!, only: [:create]
4 |
5 | def create
6 | sign_in_with_access_token!(auth_hash.credentials)
7 |
8 | redirect_to session.delete(:rails_sso_return_path) || root_path
9 | end
10 |
11 | def destroy
12 | sign_out!
13 |
14 | redirect_to root_path
15 | end
16 |
17 | protected
18 |
19 | def auth_hash
20 | request.env['omniauth.auth']
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/services/rails_sso/fetch_user.rb:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | module RailsSso
4 | class FetchUser
5 | def initialize(client)
6 | @client = client
7 | end
8 |
9 | def call
10 | response = client.get(RailsSso.config.provider_profile_path)
11 |
12 | case response.status
13 | when 200
14 | begin
15 | JSON.parse(response.body)
16 | rescue
17 | response.body
18 | end
19 | when 401
20 | raise ResponseError.new(:unauthenticated)
21 | else
22 | raise ResponseError.new(:unknown)
23 | end
24 | end
25 |
26 | private
27 |
28 | attr_reader :client
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | ruby:
3 | version: 2.1.5
4 |
5 | database:
6 | override:
7 | - echo "no db needed"
8 |
9 | notify:
10 | webhooks:
11 | - url: https://webhooks.gitter.im/e/5d1db47506eeaff9fc58
12 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | rails_sso:
3 | errors:
4 | unauthenticated: "You're not authenticated"
5 | unknown: "Something wrong happened"
6 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | RailsSso::Engine.routes.draw do
2 | scope module: 'rails_sso' do
3 | get '/:provider/callback', to: 'sessions#create'
4 | delete '/sign_out', to: 'sessions#destroy', as: :sign_out
5 |
6 | root to: redirect("/auth/#{RailsSso.config.provider_name}"), as: :sign_in
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/generators/rails_sso_generator.rb:
--------------------------------------------------------------------------------
1 | class RailsSsoGenerator < Rails::Generators::Base
2 | source_root File.expand_path("../templates", __FILE__)
3 |
4 | desc "Creates RailsSso initializer and mount sso routes."
5 |
6 | def copy_initializer
7 | template "sso.rb", "config/initializers/sso.rb"
8 | end
9 |
10 | def add_sso_routes
11 | route "mount RailsSso::Engine => '/sso', as: 'sso'"
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/generators/templates/sso.rb:
--------------------------------------------------------------------------------
1 | RailsSso.configure do |config|
2 | # include RailsSso::Helpers to ActionController::Base
3 | config.magic_enabled = true
4 | # url of entity provider
5 | config.provider_url = 'https://example.com'
6 | # name of oauth2 provider
7 | config.provider_name = 'example'
8 | # oauth keys for omniauth-example
9 | config.provider_key = ENV['PROVIDER_KEY']
10 | config.provider_secret = ENV['PROVIDER_SECRET']
11 | # path for fetching user data
12 | config.provider_profile_path = '/api/v1/profile'
13 | # set if you support single sign out
14 | config.provider_sign_out_path = '/api/v1/session'
15 | # enable cache (will use Rails.cache store)
16 | config.use_cache = Rails.application.config.action_controller.perform_caching
17 | end
18 |
--------------------------------------------------------------------------------
/lib/rails_sso.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | class Error < RuntimeError; end
3 |
4 | mattr_accessor :config
5 |
6 | def self.configure
7 | self.config = Configuration.new
8 |
9 | yield config
10 | end
11 | end
12 |
13 | require "warden"
14 | require "omniauth-oauth2"
15 | require "rails_sso/version"
16 | require "rails_sso/app"
17 | require "rails_sso/configuration"
18 | require "rails_sso/engine"
19 | require "rails_sso/helpers"
20 | require "rails_sso/client"
21 | require "rails_sso/response_error"
22 | require "rails_sso/sso_strategy"
23 | require "rails_sso/failure_app"
24 | require "rails_sso/token_mock"
25 |
--------------------------------------------------------------------------------
/lib/rails_sso/app.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | class App
3 | attr_reader :strategy, :session, :provider_client
4 |
5 | def initialize(strategy, session, provider_client)
6 | @strategy, @session, @provider_client = strategy, session, provider_client
7 | end
8 |
9 | def fetch_user_data
10 | FetchUser.new(provider_client.token!(current_access_token_value)).call
11 | rescue ResponseError => e
12 | refresh_access_token! do
13 | FetchUser.new(provider_client.token!(current_access_token_value)).call
14 | end if e.code == :unauthenticated
15 | end
16 |
17 | def refresh_access_token!
18 | save_access_token!(access_token.refresh!)
19 |
20 | yield if block_given?
21 | rescue ::OAuth2::Error
22 | nil
23 | rescue ResponseError => e
24 | nil if e.code == :unauthenticated
25 | end
26 |
27 | def save_access_token!(access_token)
28 | session[:access_token] = access_token.token
29 | session[:refresh_token] = access_token.refresh_token
30 | end
31 |
32 | def access_token
33 | return token_mock if RailsSso.config.test_mode?
34 |
35 | OAuth2::AccessToken.new(strategy.client, session[:access_token], {
36 | refresh_token: session[:refresh_token]
37 | })
38 | end
39 |
40 | def invalidate_access_token!
41 | if RailsSso.config.provider_sign_out_path
42 | access_token.delete(RailsSso.config.provider_sign_out_path)
43 | end
44 | end
45 |
46 | private
47 |
48 | def current_access_token_value
49 | session[:access_token] or current_access_token_mock
50 | end
51 |
52 | def current_access_token_mock
53 | if RailsSso.config.test_mode?
54 | token_mock.token
55 | end
56 | end
57 |
58 | def token_mock
59 | @token_mock ||= RailsSso::TokenMock.new
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/lib/rails_sso/client.rb:
--------------------------------------------------------------------------------
1 | require 'faraday'
2 | require 'faraday-http-cache'
3 |
4 | module RailsSso
5 | class Client
6 | def self.build(url, &block)
7 | adapter = Faraday.new(url, &block)
8 | new(adapter)
9 | end
10 |
11 | def self.build_real(url)
12 | build(url) do |conn|
13 | if RailsSso.config.use_cache
14 | conn.use :http_cache,
15 | store: Rails.cache,
16 | logger: Rails.logger,
17 | serializer: Marshal,
18 | shared_cache: false
19 | end
20 |
21 | conn.adapter Faraday.default_adapter
22 | end
23 | end
24 |
25 | def self.build_fake(url)
26 | build(url) do |conn|
27 | conn.adapter :test do |stub|
28 | RailsSso.config.profile_mocks.each do |token, profile|
29 | headers = {
30 | "Content-Type" => "application/json",
31 | "Authorization" => "Bearer #{token}"
32 | }
33 |
34 | stub.get(RailsSso.config.provider_profile_path, headers) do |env|
35 | if profile.nil?
36 | [401, { "Content-Type" => "application/json" }, ""]
37 | else
38 | [200, { "Content-Type" => "application/json" }, profile.to_json]
39 | end
40 | end
41 | end
42 | end
43 | end
44 | end
45 |
46 | def initialize(adapter)
47 | @connection = adapter
48 | end
49 |
50 | def token!(token)
51 | @token = token
52 |
53 | self
54 | end
55 |
56 | def get(url, params = {})
57 | request(:get, url, params)
58 | end
59 |
60 | def post(url, params = {})
61 | request(:post, url, params)
62 | end
63 |
64 | def put(url, params = {})
65 | request(:put, url, params)
66 | end
67 |
68 | def delete(url, params = {})
69 | request(:delete, url, params)
70 | end
71 |
72 | def patch(url, params = {})
73 | request(:patch, url, params)
74 | end
75 |
76 | private
77 |
78 | attr_reader :connection, :token
79 |
80 | def request(verb, url, params = {})
81 | connection.send(verb) do |req|
82 | req.headers["Authorization"] = "Bearer #{token}"
83 | req.headers["Content-Type"] = "application/json"
84 |
85 | req.url(url)
86 | req.body = params.to_json
87 | end
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/lib/rails_sso/configuration.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | class Configuration
3 | attr_accessor :application_controller
4 | attr_accessor :magic_enabled
5 |
6 | attr_writer :provider_url
7 | attr_accessor :provider_name
8 | attr_accessor :provider_key
9 | attr_accessor :provider_secret
10 |
11 | attr_accessor :provider_profile_path
12 | attr_accessor :provider_sign_out_path
13 |
14 | attr_accessor :use_cache
15 |
16 | attr_reader :test_mode
17 | attr_accessor :profile_mocks
18 | attr_accessor :access_token_mock
19 |
20 | attr_accessor :failure_app
21 |
22 | def initialize
23 | self.application_controller = "ApplicationController"
24 | self.magic_enabled = true
25 | self.use_cache = false
26 | self.test_mode = false
27 | self.profile_mocks = {}
28 | self.access_token_mock = nil
29 | self.failure_app = RailsSso::FailureApp
30 | end
31 |
32 | def provider_callback_path
33 | "/sso/#{provider_name}/callback"
34 | end
35 |
36 | def oauth2_strategy_class
37 | OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(provider_name.to_s)}")
38 | end
39 |
40 | def test_mode=(value)
41 | @test_mode = value
42 | OmniAuth.config.test_mode = value
43 | end
44 |
45 | def profile_mock
46 | profile_mocks.fetch(access_token_mock) do
47 | fail %Q{Mock "#{access_token_mock}" has not been setup!}
48 | end
49 | end
50 |
51 | def test_mode?
52 | @test_mode
53 | end
54 |
55 | def provider_url
56 | fail RailsSso::Error, "Provider url not set!" if @provider_url.nil?
57 |
58 | @provider_url
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/rails_sso/engine.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | class Engine < Rails::Engine
3 | initializer "sso.helpers" do
4 | if RailsSso.config.magic_enabled
5 | ActiveSupport.on_load(:action_controller) do
6 | include RailsSso::Helpers
7 | end
8 | end
9 | end
10 |
11 | initializer "sso.omniauth", after: :load_config_initializers, before: :build_middleware_stack do |app|
12 | if RailsSso.config.provider_name
13 | RailsSso.config.oauth2_strategy_class.class_eval do
14 | def setup_phase
15 | setup_sso!
16 |
17 | super
18 | end
19 |
20 | def other_phase
21 | setup_sso!
22 |
23 | call_app!
24 | end
25 |
26 | def mock_call!(*)
27 | setup_sso!
28 |
29 | super
30 | end
31 |
32 | def setup_sso!
33 | env["sso"] ||= RailsSso::App.new(self, session, sso_client)
34 | end
35 |
36 | def sso_client
37 | if RailsSso.config.test_mode
38 | RailsSso::Client.build_fake(RailsSso.config.provider_url)
39 | else
40 | RailsSso::Client.build_real(RailsSso.config.provider_url)
41 | end
42 | end
43 | end
44 |
45 | app.config.middleware.use OmniAuth::Builder do
46 | provider RailsSso.config.provider_name,
47 | RailsSso.config.provider_key,
48 | RailsSso.config.provider_secret,
49 | callback_path: RailsSso.config.provider_callback_path
50 | end
51 |
52 | app.config.middleware.insert_after OmniAuth::Builder, Warden::Manager do |manager|
53 | manager.default_strategies :sso
54 | manager.failure_app = RailsSso.config.failure_app
55 | end
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/rails_sso/failure_app.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | class FailureApp < ::ActionController::Metal
3 | include ActionController::Redirecting
4 | include RailsSso::Engine.routes.url_helpers
5 |
6 | def self.call(env)
7 | @respond ||= action(:respond)
8 | @respond.call(env)
9 | end
10 |
11 | def respond
12 | if request.xhr?
13 | self.status = :unauthorized
14 | self.content_type = request.content_type
15 | self.response_body = ''
16 | else
17 | redirect_to sign_in_path
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/rails_sso/helpers.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | module Helpers
3 | def self.included(base)
4 | base.class_eval do
5 | helper_method :current_user_data, :user_signed_in?
6 | end
7 | end
8 |
9 | def current_user_data
10 | @current_user_data ||= warden.authenticate
11 | end
12 |
13 | def authenticate_user!
14 | unless user_signed_in?
15 | session[:rails_sso_return_path] = request.path
16 | end
17 |
18 | warden.authenticate!
19 | end
20 |
21 | def user_signed_in?
22 | warden.authenticate?
23 | end
24 |
25 | def sign_in_with_access_token!(access_token)
26 | sso_app.save_access_token!(access_token)
27 | end
28 |
29 | def sign_out!
30 | sso_app.invalidate_access_token!
31 |
32 | warden.logout
33 | end
34 |
35 | def warden
36 | request.env['warden']
37 | end
38 |
39 | def sso_app
40 | request.env['sso']
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/rails_sso/response_error.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | class ResponseError < StandardError
3 | attr_reader :code
4 |
5 | def initialize(code)
6 | @code = code
7 |
8 | super(I18n.t("rails_sso.errors.#{code}"))
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/rails_sso/sso_strategy.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | class SsoStrategy < ::Warden::Strategies::Base
3 | def store?
4 | false
5 | end
6 |
7 | def valid?
8 | session[:access_token].present? || access_token_mock
9 | end
10 |
11 | def authenticate!
12 | user = env["sso"].fetch_user_data
13 |
14 | case
15 | when user.nil?
16 | fail! "strategies.sso.failed"
17 | else
18 | success! user
19 | end
20 | end
21 |
22 | private
23 |
24 | def access_token_mock
25 | RailsSso.config.access_token_mock if RailsSso.config.test_mode
26 | end
27 | end
28 | end
29 |
30 | Warden::Strategies.add(:sso, RailsSso::SsoStrategy)
31 |
--------------------------------------------------------------------------------
/lib/rails_sso/token_mock.rb:
--------------------------------------------------------------------------------
1 | require 'faraday'
2 |
3 | module RailsSso
4 | class TokenMock
5 | attr_reader :connection
6 |
7 | delegate :get, :post, :put, :patch, :delete, to: :connection
8 |
9 | def initialize(*)
10 | @connection = Faraday.new do |builder|
11 | builder.adapter :test do |stub|
12 | stub.delete(RailsSso.config.provider_sign_out_path) { |env| [200, {}, ''] }
13 | end
14 | end
15 | end
16 |
17 | def token
18 | RailsSso.config.access_token_mock
19 | end
20 |
21 | def refresh_token
22 | nil
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/rails_sso/version.rb:
--------------------------------------------------------------------------------
1 | module RailsSso
2 | VERSION = "0.7.5"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/tasks/rails_sso_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :rails_sso do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/rails_sso.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | # Maintain your gem's version:
4 | require "rails_sso/version"
5 |
6 | # Describe your gem and declare its dependencies:
7 | Gem::Specification.new do |s|
8 | s.name = "rails_sso"
9 | s.version = RailsSso::VERSION
10 | s.authors = ["Jan Dudulski"]
11 | s.email = ["jan@dudulski.pl"]
12 | s.homepage = "https://github.com/monterail/rails_sso"
13 | s.summary = "SSO Rails Engine"
14 | s.description = "Single Sign On solution via OAuth2 for Ruby on Rails."
15 | s.license = "MIT"
16 |
17 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
18 | s.test_files = Dir["test/**/*"]
19 |
20 | s.add_dependency "rails", "~> 4.1"
21 | s.add_dependency "faraday-http-cache", "~> 1.0"
22 | s.add_dependency "omniauth-oauth2", "~> 1.2", "< 1.4"
23 | s.add_dependency "warden", "~> 1.2"
24 |
25 | s.add_development_dependency "sqlite3"
26 | s.add_development_dependency "anima"
27 | s.add_development_dependency "mocha"
28 | s.add_development_dependency "pry"
29 | end
30 |
--------------------------------------------------------------------------------
/test/controllers/rails_sso/sessions_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RailsSso::SessionsControllerTest < ActionController::TestCase
4 | def setup
5 | @routes = RailsSso::Engine.routes
6 |
7 | @auth_hash = OmniAuth::AuthHash.new({
8 | provider: 'developer',
9 | uid: '1',
10 | name: 'Kowalski',
11 | email: 'jan@kowalski.pl',
12 | key: 'value'
13 | })
14 |
15 | OmniAuth.config.mock_auth[:developer] = @auth_hash
16 |
17 | request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:developer]
18 | end
19 |
20 | def teardown
21 | OmniAuth.config.mock_auth[:developer] = nil
22 | end
23 |
24 | test 'create should save access token and redirect to root path' do
25 | @controller.expects(:sign_in_with_access_token!).with(@auth_hash.credentials).once
26 |
27 | get :create, { provider: 'developer' }
28 |
29 | assert_redirected_to main_app.root_path
30 | end
31 |
32 | test 'destroy should invalidate access token and redirect to root path' do
33 | @controller.expects(:sign_out!).once
34 |
35 | delete :destroy, {}, { access_token: 'abc', refresh_token: 'def' }
36 |
37 | assert_redirected_to main_app.root_path
38 | end
39 |
40 | def main_app
41 | Rails.application.class.routes.url_helpers
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/test/dummy/README.rdoc:
--------------------------------------------------------------------------------
1 | == README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
26 |
27 | Please feel free to use a different markup language if you do not plan to run
28 | rake doc:app.
29 |
--------------------------------------------------------------------------------
/test/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monterail/rails_sso/895e6f4a5a574466bf517b998ac15d2cce4e541c/test/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/test/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require_tree .
14 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | # Prevent CSRF attacks by raising an exception.
3 | # For APIs, you may want to use :null_session instead.
4 | protect_from_forgery with: :exception
5 |
6 | def index
7 | render text: 'Hello'
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monterail/rails_sso/895e6f4a5a574466bf517b998ac15d2cce4e541c/test/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monterail/rails_sso/895e6f4a5a574466bf517b998ac15d2cce4e541c/test/dummy/app/mailers/.keep
--------------------------------------------------------------------------------
/test/dummy/app/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monterail/rails_sso/895e6f4a5a574466bf517b998ac15d2cce4e541c/test/dummy/app/models/.keep
--------------------------------------------------------------------------------
/test/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monterail/rails_sso/895e6f4a5a574466bf517b998ac15d2cce4e541c/test/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/test/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 |
4 | # path to your application root.
5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
6 |
7 | Dir.chdir APP_ROOT do
8 | # This script is a starting point to setup your application.
9 | # Add necessary setup steps to this file:
10 |
11 | puts "== Installing dependencies =="
12 | system "gem install bundler --conservative"
13 | system "bundle check || bundle install"
14 |
15 | # puts "\n== Copying sample files =="
16 | # unless File.exist?("config/database.yml")
17 | # system "cp config/database.yml.sample config/database.yml"
18 | # end
19 |
20 | puts "\n== Preparing database =="
21 | system "bin/rake db:setup"
22 |
23 | puts "\n== Removing old logs and tempfiles =="
24 | system "rm -f log/*"
25 | system "rm -rf tmp/cache"
26 |
27 | puts "\n== Restarting application server =="
28 | system "touch tmp/restart.txt"
29 | end
30 |
--------------------------------------------------------------------------------
/test/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/test/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | %w(
4 | action_controller
5 | action_view
6 | rails/test_unit
7 | sprockets
8 | ).each do |framework|
9 | begin
10 | require "#{framework}/railtie"
11 | rescue LoadError
12 | end
13 | end
14 |
15 | Bundler.require(*Rails.groups)
16 | require "rails_sso"
17 |
18 | module Dummy
19 | class Application < Rails::Application
20 | # Settings in config/environments/* take precedence over those specified here.
21 | # Application configuration should go into files in config/initializers
22 | # -- all .rb files in that directory are automatically loaded.
23 |
24 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
25 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
26 | # config.time_zone = 'Central Time (US & Canada)'
27 |
28 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
29 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
30 | # config.i18n.default_locale = :de
31 |
32 | # Do not swallow errors in after_commit/after_rollback callbacks.
33 | # config.active_record.raise_in_transactional_callbacks = true
34 | end
35 | end
36 |
37 |
--------------------------------------------------------------------------------
/test/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
6 |
--------------------------------------------------------------------------------
/test/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/test/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | # config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | # config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
31 | # yet still be able to expire them through the digest params.
32 | config.assets.digest = true
33 |
34 | # Adds additional error checking when serving assets at runtime.
35 | # Checks for improperly declared sprockets dependencies.
36 | # Raises helpful error messages.
37 | config.assets.raise_runtime_errors = true
38 |
39 | # Raises error for missing translations
40 | # config.action_view.raise_on_missing_translations = true
41 | end
42 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like
20 | # NGINX, varnish or squid.
21 | # config.action_dispatch.rack_cache = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress JavaScripts and CSS.
28 | config.assets.js_compressor = :uglifier
29 | # config.assets.css_compressor = :sass
30 |
31 | # Do not fallback to assets pipeline if a precompiled asset is missed.
32 | config.assets.compile = false
33 |
34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35 | # yet still be able to expire them through the digest params.
36 | config.assets.digest = true
37 |
38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39 |
40 | # Specifies the header that your server uses for sending files.
41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
43 |
44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
45 | # config.force_ssl = true
46 |
47 | # Use the lowest log level to ensure availability of diagnostic information
48 | # when problems arise.
49 | config.log_level = :debug
50 |
51 | # Prepend all log lines with the following tags.
52 | # config.log_tags = [ :subdomain, :uuid ]
53 |
54 | # Use a different logger for distributed setups.
55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
61 | # config.action_controller.asset_host = 'http://assets.example.com'
62 |
63 | # Ignore bad email addresses and do not raise email delivery errors.
64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
65 | # config.action_mailer.raise_delivery_errors = false
66 |
67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
68 | # the I18n.default_locale when a translation cannot be found).
69 | config.i18n.fallbacks = true
70 |
71 | # Send deprecation notices to registered listeners.
72 | config.active_support.deprecation = :notify
73 |
74 | # Use default logging formatter so that PID and timestamp are not suppressed.
75 | config.log_formatter = ::Logger::Formatter.new
76 |
77 | # Do not dump schema after migrations.
78 | # config.active_record.dump_schema_after_migration = false
79 | end
80 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static file server for tests with Cache-Control for performance.
16 | config.serve_static_files = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | # config.action_mailer.delivery_method = :test
33 |
34 | # Randomize the order test cases are executed.
35 | config.active_support.test_order = :random
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
4 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session'
4 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/sso.rb:
--------------------------------------------------------------------------------
1 | RailsSso.configure do |config|
2 | config.provider_url = 'http://example.com'
3 | config.provider_name = 'developer'
4 | config.provider_key = 'key'
5 | config.provider_secret = 'secret'
6 | config.provider_profile_path = '/api/v1/me'
7 | config.provider_sign_out_path = '/api/v1/me'
8 | config.use_cache = false
9 | end
10 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/test/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/test/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | mount RailsSso::Engine => '/sso', as: 'sso'
3 | # The priority is based upon order of creation: first created -> highest priority.
4 | # See how all your routes lay out with "rake routes".
5 |
6 | # You can have the root of your site routed with "root"
7 | # root 'welcome#index'
8 |
9 | # Example of regular route:
10 | # get 'products/:id' => 'catalog#view'
11 |
12 | # Example of named route that can be invoked with purchase_url(id: product.id)
13 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
14 |
15 | # Example resource route (maps HTTP verbs to controller actions automatically):
16 | # resources :products
17 |
18 | # Example resource route with options:
19 | # resources :products do
20 | # member do
21 | # get 'short'
22 | # post 'toggle'
23 | # end
24 | #
25 | # collection do
26 | # get 'sold'
27 | # end
28 | # end
29 |
30 | # Example resource route with sub-resources:
31 | # resources :products do
32 | # resources :comments, :sales
33 | # resource :seller
34 | # end
35 |
36 | # Example resource route with more complex sub-resources:
37 | # resources :products do
38 | # resources :comments
39 | # resources :sales do
40 | # get 'recent', on: :collection
41 | # end
42 | # end
43 |
44 | # Example resource route with concerns:
45 | # concern :toggleable do
46 | # post 'toggle'
47 | # end
48 | # resources :posts, concerns: :toggleable
49 | # resources :photos, concerns: :toggleable
50 |
51 | # Example resource route within a namespace:
52 | # namespace :admin do
53 | # # Directs /admin/products/* to Admin::ProductsController
54 | # # (app/controllers/admin/products_controller.rb)
55 | # resources :products
56 | # end
57 | root to: 'application#index'
58 | end
59 |
--------------------------------------------------------------------------------
/test/dummy/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: 2f0d73964683106d2246b1dcfed47ba883afcb705fac9f919276409f6f21a32213aa07c31ab7b491a879f2c2c66fb6e76cb3dc8e506407c1e2fd6259cea5bada
15 |
16 | test:
17 | secret_key_base: 0332915b37c8700aee094c3bda19db8f6c8fc109ff8823648ae57a1a0547982587132a2bfe809e08e5bf6799168d7e46f329abe7769480ff34e9f32bc6b6105a
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/test/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monterail/rails_sso/895e6f4a5a574466bf517b998ac15d2cce4e541c/test/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/test/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monterail/rails_sso/895e6f4a5a574466bf517b998ac15d2cce4e541c/test/dummy/log/.keep
--------------------------------------------------------------------------------
/test/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monterail/rails_sso/895e6f4a5a574466bf517b998ac15d2cce4e541c/test/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/test/lib/rails_sso/app_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class RailsSso::AppTest < ActiveSupport::TestCase
4 | test "#fetch_user_data returns data based on access token" do
5 | strategy = mock()
6 | session = { access_token: "169783" }
7 | client = RailsSso::Client.new(build_adapter)
8 |
9 | app = RailsSso::App.new(strategy, session, client)
10 |
11 | assert_equal user_data, app.fetch_user_data
12 | end
13 |
14 | test "#fetch_user_data when stale token refreshes token and returns data" do
15 | new_token = mock()
16 | new_token.stubs(:token).returns("169783")
17 | new_token.stubs(:refresh_token).returns("new_refresh_token")
18 | new_token.stubs(:options=)
19 | adapter = build_adapter
20 | adapter.stubs(:id).returns("123")
21 | adapter.stubs(:secret).returns("456")
22 | adapter.stubs(:get_token).returns(new_token)
23 | strategy = mock()
24 | strategy.stubs(:client).returns(adapter)
25 | session = { access_token: "stale", refresh_token: "refresh_token" }
26 | client = RailsSso::Client.new(adapter)
27 |
28 | app = RailsSso::App.new(strategy, session, client)
29 |
30 | assert_equal user_data, app.fetch_user_data
31 | assert_equal "169783", session[:access_token]
32 | assert_equal "new_refresh_token", session[:refresh_token]
33 | end
34 |
35 | test "#fetch_user_data when invalid token returns nil and reset access token" do
36 | error_response = mock()
37 | error_response.stubs(:error=)
38 | error_response.stubs(:parsed).returns({
39 | "error" => "401",
40 | "error_description" => "error description"
41 | })
42 | error_response.stubs(:body).returns("")
43 | adapter = build_adapter
44 | adapter.stubs(:id).returns("123")
45 | adapter.stubs(:secret).returns("456")
46 | adapter.stubs(:get_token).raises(::OAuth2::Error.new(error_response))
47 | strategy = mock()
48 | strategy.stubs(:client).returns(adapter)
49 | session = { access_token: "invalid", refresh_token: "invalid" }
50 | client = RailsSso::Client.new(adapter)
51 |
52 | app = RailsSso::App.new(strategy, session, client)
53 |
54 | assert_equal nil, app.fetch_user_data
55 | end
56 |
57 | def user_data
58 | { "uid" => "1234" }
59 | end
60 |
61 | def valid_headers
62 | build_request_headers("169783")
63 | end
64 |
65 | def stale_headers
66 | build_request_headers("stale")
67 | end
68 |
69 | def invalid_headers
70 | build_request_headers("invalid")
71 | end
72 |
73 | def build_adapter
74 | Faraday.new do |builder|
75 | builder.adapter :test do |stub|
76 | stub.get("/api/v1/me", invalid_headers) do |env|
77 | [401, { "Content-Type" => "application/json" }, ""]
78 | end
79 | stub.get("/api/v1/me", stale_headers) do |env|
80 | [401, { "Content-Type" => "application/json" }, ""]
81 | end
82 | stub.get("/api/v1/me", valid_headers) do |env|
83 | [200, { "Content-Type" => "application/json" }, user_data]
84 | end
85 | end
86 | end
87 | end
88 |
89 | def build_request_headers(token)
90 | {
91 | "Authorization" => "Bearer #{token}",
92 | "Content-Type" => "application/json"
93 | }
94 | end
95 | end
96 |
--------------------------------------------------------------------------------
/test/lib/rails_sso/configuration_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class RailsSso::ConfigurationTest < ActiveSupport::TestCase
4 | test "#provider_url fails if not yet provided" do
5 | config = RailsSso::Configuration.new
6 |
7 | assert_raises RailsSso::Error, "Provider url not set!" do
8 | config.provider_url
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/lib/rails_sso/failure_app_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RailsSso::FailureAppTest < ActiveSupport::TestCase
4 | test "regular call runs respond action and redirects to sso" do
5 | env = {
6 | 'REQUEST_URI' => 'http://test.host',
7 | 'HTTP_HOST' => 'test.host'
8 | }
9 | response = RailsSso::FailureApp.call(env).to_a
10 |
11 | assert_equal 302, response.first
12 | assert_equal 'http://test.host/sso/', response.second['Location']
13 | end
14 |
15 | test "json call runs respond action and renders 401" do
16 | env = {
17 | 'REQUEST_URI' => 'http://test.host',
18 | 'HTTP_HOST' => 'test.host',
19 | 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'
20 | }
21 | response = RailsSso::FailureApp.call(env).to_a
22 |
23 | assert_equal 401, response.first
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/test/lib/rails_sso/helpers_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class RailsSso::HelpersTest < ActionController::TestCase
4 | tests ApplicationController
5 |
6 | def setup
7 | @mock_warden = OpenStruct.new
8 | @controller.request.env["warden"] = @mock_warden
9 | end
10 |
11 | test "provide access to warden instance" do
12 | assert_equal @mock_warden, @controller.warden
13 | end
14 |
15 | test "proxy signed_in? to authenticate?" do
16 | @mock_warden.expects(:authenticate?).once
17 | @controller.user_signed_in?
18 | end
19 |
20 | test "proxy current_user_data to authenticate" do
21 | @mock_warden.expects(:authenticate).once
22 | @controller.current_user_data
23 | end
24 |
25 | test "proxy authenticate_user! to authenticate!" do
26 | @mock_warden.expects(:authenticate!).once
27 | @controller.authenticate_user!
28 | end
29 |
30 | test "keep current path in session during authentication" do
31 | @mock_warden.stubs(:authenticate!)
32 | @request.stubs(:path).returns("something")
33 |
34 | @controller.authenticate_user!
35 |
36 | assert_equal "something", @controller.session[:rails_sso_return_path]
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/test/lib/rails_sso/response_error_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RailsSso::ResponseErrorTest < ActiveSupport::TestCase
4 | test "assigns error code" do
5 | err = RailsSso::ResponseError.new(:err_code)
6 |
7 | assert_equal err.code, :err_code
8 | end
9 |
10 | test "assigns unauthenticated error message from locales" do
11 | err = RailsSso::ResponseError.new(:unauthenticated)
12 |
13 | assert_equal err.message, "You're not authenticated"
14 | end
15 |
16 | test "assigns unknown error message from locales" do
17 | err = RailsSso::ResponseError.new(:unknown)
18 |
19 | assert_equal err.message, "Something wrong happened"
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/test/lib/rails_sso/sso_strategy_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class RailsSso::SsoStrategyTest < ActiveSupport::TestCase
4 | test "does not store" do
5 | strategy = RailsSso::SsoStrategy.new(nil)
6 |
7 | refute strategy.store?
8 | end
9 |
10 | test "is valid when access token is present" do
11 | session = { access_token: "169783" }
12 | env = { "rack.session" => session }
13 | strategy = RailsSso::SsoStrategy.new(env)
14 |
15 | assert strategy.valid?
16 | end
17 |
18 | test "is valid when in test mode with access token mock" do
19 | env = { "rack.session" => {} }
20 | strategy = RailsSso::SsoStrategy.new(env)
21 | RailsSso.config.stubs(:test_mode).returns(true)
22 | RailsSso.config.stubs(:access_token_mock).returns("169783")
23 |
24 | assert strategy.valid?
25 | end
26 |
27 | test "is invalid when in test mode without access token mock" do
28 | env = { "rack.session" => {} }
29 | strategy = RailsSso::SsoStrategy.new(env)
30 | RailsSso.config.stubs(:test_mode).returns(true)
31 | RailsSso.config.stubs(:access_token_mock).returns(nil)
32 |
33 | refute strategy.valid?
34 | end
35 |
36 | test "is invalid when access token not present" do
37 | env = { "rack.session" => {} }
38 | strategy = RailsSso::SsoStrategy.new(env)
39 |
40 | refute strategy.valid?
41 | end
42 |
43 | test "authenticate! returns :success when sso can fetch user" do
44 | sso_app = mock()
45 | sso_app.stubs(:fetch_user_data).returns({ "uid" => "169783" })
46 | env = { "sso" => sso_app }
47 | strategy = RailsSso::SsoStrategy.new(env)
48 |
49 | assert_equal :success, strategy.authenticate!
50 | end
51 |
52 | test "authenticate! returns :failure when sso cannot fetch user" do
53 | sso_app = mock()
54 | sso_app.stubs(:fetch_user_data).returns(nil)
55 | env = { "sso" => sso_app }
56 | strategy = RailsSso::SsoStrategy.new(env)
57 |
58 | assert_equal :failure, strategy.authenticate!
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/test/rails_sso_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RailsSsoTest < ActiveSupport::TestCase
4 | test "truth" do
5 | assert_kind_of Module, RailsSso
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/routes/sso_routes_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class SsoRoutesTest < ActionController::TestCase
4 | def setup
5 | @routes = RailsSso::Engine.routes
6 | end
7 |
8 | test 'should route /sign_out' do
9 | assert_routing({
10 | method: 'delete',
11 | path: '/sign_out'
12 | }, {
13 | controller: 'rails_sso/sessions',
14 | action: 'destroy'
15 | })
16 | end
17 |
18 | test 'should route /:provider/callback' do
19 | assert_routing('/example/callback', {
20 | controller: 'rails_sso/sessions',
21 | action: 'create',
22 | provider: 'example'
23 | })
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/test/services/rails_sso/fetch_user_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RailsSso::FetchUserTest < ActiveSupport::TestCase
4 | test "success call should fetch user with access token and return parsed data" do
5 | data = RailsSso::FetchUser.new(success_client).call
6 |
7 | assert_equal data['name'], user_data['name']
8 | assert_equal data['email'], user_data['email']
9 | end
10 |
11 | test "unauthenticated call should raise error" do
12 | err = assert_raises(RailsSso::ResponseError) { RailsSso::FetchUser.new(unauthenticated_client).call }
13 | assert_equal :unauthenticated, err.code
14 | end
15 |
16 | test "unknown call should raise error" do
17 | err = assert_raises(RailsSso::ResponseError) { RailsSso::FetchUser.new(unknown_client).call }
18 | assert_equal :unknown, err.code
19 | end
20 |
21 | def user_data
22 | {
23 | 'name' => 'Kowalski',
24 | 'email' => 'jan@kowalski.pl'
25 | }
26 | end
27 |
28 | def response_headers
29 | { 'Content-Type' => 'application/json' }
30 | end
31 |
32 | def success_client
33 | Faraday.new do |builder|
34 | builder.adapter :test do |stub|
35 | stub.get('/api/v1/me') { |env| [200, response_headers, user_data] }
36 | end
37 | end
38 | end
39 |
40 | def unauthenticated_client
41 | Faraday.new do |builder|
42 | builder.adapter :test do |stub|
43 | stub.get('/api/v1/me') { |env| [401, response_headers, {}] }
44 | end
45 | end
46 | end
47 |
48 | def unknown_client
49 | Faraday.new do |builder|
50 | builder.adapter :test do |stub|
51 | stub.get('/api/v1/me') { |env| [500, response_headers, {}] }
52 | end
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "codeclimate-test-reporter"
2 | CodeClimate::TestReporter.start
3 | # Configure Rails Environment
4 | ENV["RAILS_ENV"] = "test"
5 |
6 | require File.expand_path("../../test/dummy/config/environment.rb", __FILE__)
7 | require "rails/test_help"
8 | require "mocha/mini_test"
9 |
10 | # Filter out Minitest backtrace while allowing backtrace from other libraries
11 | # to be shown.
12 | Minitest.backtrace_filter = Minitest::BacktraceFilter.new
13 |
14 | # Load support files
15 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
16 |
17 | # Load fixtures from the engine
18 | if ActiveSupport::TestCase.respond_to?(:fixture_path=)
19 | ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
20 | end
21 |
--------------------------------------------------------------------------------