From 83d1a2433be882f71047ad7ab13b942d39b92ea9 Mon Sep 17 00:00:00 2001 From: Trey Pendragon Date: Mon, 31 Jul 2017 11:06:19 -0700 Subject: [PATCH] Add CAS login Closes #23 --- .rubocop.yml | 1 + Gemfile | 1 + Gemfile.lock | 9 +++++++++ app/controllers/application_controller.rb | 6 ++++++ .../users/omniauth_callbacks_controller.rb | 10 ++++++++++ app/models/user.rb | 12 +++++++++--- config/initializers/devise.rb | 7 ++++--- config/role_map.yml | 5 +++-- config/routes.rb | 6 +++++- db/migrate/20170731174240_add_cas_to_users.rb | 9 +++++++++ db/schema.rb | 6 +++++- spec/factories/user.rb | 7 ++++--- spec/features/root_spec.rb | 13 +++++++++++++ spec/models/user_spec.rb | 14 ++++++++++++-- spec/support/features/session_helpers.rb | 18 ++++++++++++++++++ spec/support/reset_auth.rb | 8 ++++++++ 16 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 app/controllers/users/omniauth_callbacks_controller.rb create mode 100644 db/migrate/20170731174240_add_cas_to_users.rb create mode 100644 spec/features/root_spec.rb create mode 100644 spec/support/features/session_helpers.rb create mode 100644 spec/support/reset_auth.rb diff --git a/.rubocop.yml b/.rubocop.yml index 7a32509308..80045a996d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -61,3 +61,4 @@ RSpec/VerifiedDoubles: - 'spec/models/search_builder_spec.rb' - 'spec/validators/viewing_hint_validator_spec.rb' - 'spec/validators/viewing_direction_validator_spec.rb' + - 'spec/models/user_spec.rb' diff --git a/Gemfile b/Gemfile index e9cf41d42b..e86da72dc9 100644 --- a/Gemfile +++ b/Gemfile @@ -61,6 +61,7 @@ gem 'devise' gem 'hydra-head' gem 'modernizr-rails' gem 'normalize-rails' +gem 'omniauth-cas' gem 'pul_metadata_services', github: 'pulibrary/pul_metadata_services', branch: 'master' gem 'sidekiq' gem 'string_rtl' diff --git a/Gemfile.lock b/Gemfile.lock index 6e6d2b6e6d..1e563fdec7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -300,6 +300,7 @@ GEM hamster (3.0.0) concurrent-ruby (~> 1.0) hashdiff (0.3.4) + hashie (3.5.6) honeybadger (3.1.2) httmultiparty (0.3.16) httparty (>= 0.7.3) @@ -412,6 +413,13 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) + omniauth (1.6.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) + omniauth-cas (1.1.1) + addressable (~> 2.3) + nokogiri (~> 1.5) + omniauth (~> 1.2) open4 (1.3.4) orm_adapter (0.5.0) os (0.9.6) @@ -674,6 +682,7 @@ DEPENDENCIES listen modernizr-rails normalize-rails + omniauth-cas pg poltergeist pry-byebug diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8109c787bf..74d06efcc0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,4 +5,10 @@ class ApplicationController < ActionController::Base layout 'application' protect_from_forgery with: :exception + + def guest_uid_authentication_key(key) + key &&= nil unless key.to_s =~ /^guest/ + return key if key + "guest_" + guest_user_unique_suffix + end end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb new file mode 100644 index 0000000000..f3a5e9fa96 --- /dev/null +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController + def cas + # You need to implement the method below in your model (e.g. app/models/user.rb) + @user = User.from_omniauth(request.env["omniauth.auth"]) + + sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated + set_flash_message(:notice, :success, kind: "CAS") if is_navigational_format? + end +end diff --git a/app/models/user.rb b/app/models/user.rb index c2d7899707..27d8a0f8d2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,16 +6,22 @@ class User < ApplicationRecord # Connects this user object to Blacklights Bookmarks. include Blacklight::User include Hydra::User + def self.from_omniauth(access_token) + User.where(provider: access_token.provider, uid: access_token.uid).first_or_create do |user| + user.uid = access_token.uid + user.provider = access_token.provider + user.email = "#{access_token.uid}@princeton.edu" + end + end # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable - devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :trackable, :validatable + devise :omniauthable, omniauth_providers: [:cas] # Method added by Blacklight; Blacklight uses #to_s on your # user class to get a user-displayable login/identifier for # the account. def to_s - email + uid end def admin? diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 82c5abb5b5..ca46ed129c 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -35,7 +35,7 @@ # session. If you need permissions, you should implement that in a before filter. # You can also supply a hash where the value is a boolean determining whether # or not authentication should be aborted when the value is not present. - # config.authentication_keys = [:email] + config.authentication_keys = [:uid] # Configure parameters from the request object used for authentication. Each entry # given should be a request method and it will automatically be passed to the @@ -47,12 +47,12 @@ # Configure which authentication keys should be case-insensitive. # These keys will be downcased upon creating or modifying a user and when used # to authenticate or find a user. Default is :email. - config.case_insensitive_keys = [:email] + config.case_insensitive_keys = [:uid] # Configure which authentication keys should have whitespace stripped. # These keys will have whitespace before and after removed upon creating or # modifying a user and when used to authenticate or find a user. Default is :email. - config.strip_whitespace_keys = [:email] + config.strip_whitespace_keys = [:uid] # Tell if authentication through request.params is enabled. True by default. # It can be set to an array that will enable params authentication only for the @@ -252,6 +252,7 @@ # Add a new OmniAuth provider. Check the wiki for more information on setting # up on your models and hooks. # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + config.omniauth :cas, host: 'fed.princeton.edu', url: 'https://fed.princeton.edu/cas' # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or diff --git a/config/role_map.yml b/config/role_map.yml index 013375c4ef..a1d1869287 100644 --- a/config/role_map.yml +++ b/config/role_map.yml @@ -1,10 +1,11 @@ development: admin: - - admin@example.com + - admin + - tpend test: admin: - - admin@example.com + - admin production: # Add roles for users here. diff --git a/config/routes.rb b/config/routes.rb index 2a079f1732..88ba371314 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,7 +14,11 @@ end end - devise_for :users + devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }, skip: [:passwords, :registration] + devise_scope :user do + get 'sign_out', to: 'devise/sessions#destroy', as: :destroy_user_session + get 'users/auth/cas', to: 'users/omniauth_authorize#passthru', defaults: { provider: :cas }, as: "new_user_session" + end concern :exportable, Blacklight::Routes::Exportable.new resources :solr_documents, only: [:show], path: '/catalog', controller: 'catalog' do diff --git a/db/migrate/20170731174240_add_cas_to_users.rb b/db/migrate/20170731174240_add_cas_to_users.rb new file mode 100644 index 0000000000..fff4dac9ea --- /dev/null +++ b/db/migrate/20170731174240_add_cas_to_users.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +class AddCasToUsers < ActiveRecord::Migration[5.1] + def change + add_column :users, :provider, :string + add_index :users, :provider + add_column :users, :uid, :string + add_index :users, :uid + end +end diff --git a/db/schema.rb b/db/schema.rb index 5b277e758f..78372d91a8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170724192309) do +ActiveRecord::Schema.define(version: 20170731174240) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -74,8 +74,12 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "guest", default: false + t.string "provider" + t.string "uid" t.index ["email"], name: "index_users_on_email", unique: true + t.index ["provider"], name: "index_users_on_provider" t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + t.index ["uid"], name: "index_users_on_uid" end end diff --git a/spec/factories/user.rb b/spec/factories/user.rb index 4b82904bdb..556fd90218 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true FactoryGirl.define do factory :user do - sequence(:email) { |_n| "email-#{srand}@example.com" } - password 'a password' - password_confirmation 'a password' + sequence(:email) { |_n| "#{srand}@princeton.edu" } + sequence(:uid) { |_n| "#{srand}@princeton.edu" } + provider 'cas' end factory :admin, parent: :user do email "admin@example.com" + uid 'admin' end end diff --git a/spec/features/root_spec.rb b/spec/features/root_spec.rb new file mode 100644 index 0000000000..77b7778f90 --- /dev/null +++ b/spec/features/root_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe "Home Page" do + let(:user) { FactoryGirl.create(:admin) } + before do + sign_in user + end + + it "displays creation links for administrators" do + expect(page).to have_link "New Scanned Resource" + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f5d1871186..7b0b98ce21 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4,8 +4,8 @@ RSpec.describe User, type: :model do subject(:user) { FactoryGirl.create(:user) } describe "#to_s" do - it "returns the user's email" do - expect(user.to_s).to eq user.email + it "returns the user's NetID" do + expect(user.to_s).to eq user.uid end end describe "#admin?" do @@ -14,4 +14,14 @@ expect(user).to be_admin end end + + describe ".from_omniauth" do + it "creates a user" do + token = double("token", provider: "cas", uid: "test") + user = described_class.from_omniauth(token) + expect(user).to be_persisted + expect(user.provider).to eq "cas" + expect(user.uid).to eq "test" + end + end end diff --git a/spec/support/features/session_helpers.rb b/spec/support/features/session_helpers.rb new file mode 100644 index 0000000000..69d813e6d1 --- /dev/null +++ b/spec/support/features/session_helpers.rb @@ -0,0 +1,18 @@ +module Features + # Provides methods for login and logout within Feature Tests + module SessionHelpers + # Use this in feature tests + def sign_in(who = :user) + user = if who.instance_of?(User) + who.uid + else + FactoryGirl.create(:user).uid + end + OmniAuth.config.add_mock(:cas, uid: user) + visit user_cas_omniauth_authorize_path + end + end +end +RSpec.configure do |config| + config.include Features::SessionHelpers, type: :feature +end diff --git a/spec/support/reset_auth.rb b/spec/support/reset_auth.rb new file mode 100644 index 0000000000..4d5ed1abfa --- /dev/null +++ b/spec/support/reset_auth.rb @@ -0,0 +1,8 @@ +OmniAuth.config.test_mode = true +RSpec.configure do |config| + config.before(:each) do + OmniAuth.config.mock_auth[:cas] = nil + Rails.application.env_config["devise.mapping"] = Devise.mappings[:user] # If using Devise + Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter] + end +end