├── .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 | [![Join the chat at https://gitter.im/monterail/rails_sso](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/monterail/rails_sso?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | [![Circle CI](https://circleci.com/gh/monterail/rails_sso/tree/master.svg?style=shield&circle-token=237c44548fb2c2597bcd0bc7b1dd99c81329e574)](https://circleci.com/gh/monterail/rails_sso/tree/master) 6 | [![Dependency Status](https://gemnasium.com/monterail/rails_sso.svg)](https://gemnasium.com/monterail/rails_sso) 7 | [![Gem Version](https://badge.fury.io/rb/rails_sso.svg)](http://badge.fury.io/rb/rails_sso) 8 | [![Code Climate](https://codeclimate.com/github/monterail/rails_sso/badges/gpa.svg)](https://codeclimate.com/github/monterail/rails_sso) 9 | [![Test Coverage](https://codeclimate.com/github/monterail/rails_sso/badges/coverage.svg)](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 | --------------------------------------------------------------------------------