From 64fdea36e26d695a9f4ead73edaab55b8cabbf03 Mon Sep 17 00:00:00 2001 From: Adnan Hemani Date: Thu, 28 Mar 2019 19:17:16 -0500 Subject: [PATCH] Login oauth workaround (#33) * finished adding API key to form * worked oauth into the app * fix tests * pushing secrets for travis * updated tarfile --- .gitignore | 1 + Gemfile | 2 + Gemfile.lock | 19 +++++++ app/assets/images/sign_in_google_button.png | Bin 0 -> 4099 bytes app/assets/stylesheets/application.css.scss | 4 ++ app/controllers/sessions_controller.rb | 37 +++++++++++--- app/models/user.rb | 24 +++++++++ app/views/sessions/_form.html.erb | 16 +++++- app/views/sessions/new_api_key.html.erb | 24 +++++++++ config/initializers/omniauth.rb | 9 ++++ config/locales/en.yml | 4 ++ config/routes.rb | 5 ++ db/development | Bin 88064 -> 108544 bytes db/test | Bin 12288 -> 12288 bytes secrets.tar.enc | Bin 4112 -> 7696 bytes spec/controllers/dashboard_controller_spec.rb | 7 ++- spec/controllers/sessions_controller_spec.rb | 40 ++++++++++++++- spec/models/project_spec.rb | 8 ++- spec/models/user_spec.rb | 48 ++++++++++++++++++ 19 files changed, 236 insertions(+), 12 deletions(-) create mode 100644 app/assets/images/sign_in_google_button.png create mode 100644 app/views/sessions/new_api_key.html.erb create mode 100644 config/initializers/omniauth.rb diff --git a/.gitignore b/.gitignore index 68d0828..cb510ee 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ .DS_Store credentials.json +credentials_oauth.json token.yaml diff --git a/Gemfile b/Gemfile index c083259..998fb63 100644 --- a/Gemfile +++ b/Gemfile @@ -29,6 +29,8 @@ gem 'pivotal-tracker' gem 'backtop' gem 'bson_ext' gem 'google-api-client', '~> 0.8' +gem "omniauth-google-oauth2" +gem 'omniauth-oauth2', '~> 1.3.1' gem 'rspec-rails', '~> 3.6.1', group: [:test, :development] diff --git a/Gemfile.lock b/Gemfile.lock index 1bc3cbd..714eefb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -177,6 +177,7 @@ GEM multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (~> 0.7) + hashie (3.6.0) http-cookie (1.0.3) domain_name (~> 0.5) http_parser.rb (0.6.0) @@ -213,6 +214,7 @@ GEM metaclass (~> 0.0.1) multi_json (1.13.1) multi_test (0.1.2) + multi_xml (0.6.0) multipart-post (2.0.0) netrc (0.11.0) newrelic_rpm (6.0.0.351) @@ -220,6 +222,21 @@ GEM mini_portile2 (~> 2.4.0) nokogiri-happymapper (0.6.0) nokogiri (~> 1.5) + oauth2 (1.4.1) + faraday (>= 0.8, < 0.16.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + omniauth (1.9.0) + hashie (>= 3.4.6, < 3.7.0) + rack (>= 1.6.2, < 3) + omniauth-google-oauth2 (0.2.6) + omniauth (> 1.0) + omniauth-oauth2 (~> 1.1) + omniauth-oauth2 (1.3.1) + oauth2 (~> 1.0) + omniauth (~> 1.2) os (1.0.0) pg (0.21.0) pivotal-tracker (0.5.13) @@ -407,6 +424,8 @@ DEPENDENCIES json mocha newrelic_rpm + omniauth-google-oauth2 + omniauth-oauth2 (~> 1.3.1) pg (~> 0.21) pivotal-tracker rack_session_access diff --git a/app/assets/images/sign_in_google_button.png b/app/assets/images/sign_in_google_button.png new file mode 100644 index 0000000000000000000000000000000000000000..29ab51106ce90afc978348404b55d340f801b31c GIT binary patch literal 4099 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 literal 0 HcmV?d00001 diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 1f408a7..92b2d0d 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -380,4 +380,8 @@ body { padding: 20px 30px; } } +} + +.font-size-lg { + font-size:18px; } \ No newline at end of file diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index f6565bc..b7504c6 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -6,7 +6,37 @@ def new def create user = User.authenticate(session_params) + create_session_helper(user, t('flashes.sessions.failed')) + end + + def destroy + reset_session + redirect_to :login, notice: t('flashes.sessions.destroy') + end + + def new_api_key + redirect_to :root if user_signed_in? + end + + def set_api_key + # Take email and API key, set new user + # Then redirect to login with success flash noting that API key saved + if params[:api_key].blank? or params[:username].blank? + redirect_to :new_api_key, alert: t('flashes.sessions.token_not_set') + else + User.set_new_api_key(params) + redirect_to :login, notice: t('flashes.sessions.token_set') + end + end + + def google_oauth_login + # Find out which user logged in successfully + # Set session, then redirect to root + this_user = User.authenticate_after_oauth(env["omniauth.auth"]["info"]["email"]) + create_session_helper(this_user, t('flashes.sessions.without_token')) + end + def create_session_helper(user, potential_failure_msg) if user session[:user] = { username: user.username, @@ -15,15 +45,10 @@ def create redirect_to :root, notice: t('flashes.sessions.success') else - redirect_to :login, alert: t('flashes.sessions.failed') + redirect_to :login, alert: potential_failure_msg end end - def destroy - reset_session - redirect_to :login, notice: t('flashes.sessions.destroy') - end - private def session_params diff --git a/app/models/user.rb b/app/models/user.rb index f53decb..1500e60 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -55,6 +55,30 @@ def salted(username) def encrypt(password, salt) Digest::SHA1.hexdigest("--#{salt}--#{password}--") end + + def set_new_api_key(params) + this_user = User.find_by_username(params[:username]) + if not this_user + this_user = User.new( + username: params[:username], + password: (0...8).map { (65 + rand(26)).chr }.join, + token: params[:api_key] + ) + else + this_user.token = params[:api_key] + end + this_user.save! + end + + def authenticate_after_oauth(username) + # Returns user if found and has API token, nil if not + this_user = User.find_by_username(username) + if this_user and not this_user.token.empty? + this_user + else + nil + end + end end # Checks passwords against crypted password diff --git a/app/views/sessions/_form.html.erb b/app/views/sessions/_form.html.erb index 2410ca4..3f40ebf 100644 --- a/app/views/sessions/_form.html.erb +++ b/app/views/sessions/_form.html.erb @@ -9,4 +9,18 @@ <%= submit_tag t('signin').upcase, class: 'btn btn-success' %> -<% end %> \ No newline at end of file +<% end %> + +
+
+ +
+ + <%= link_to "/auth/google_oauth2" do %> + <%= image_tag('sign_in_google_button.png') %> + <% end %> +

+ +

First time signing in with Google? Set your API token below.

+ <%= button_to "Set API Token", new_api_key_path, method: :get, class: 'btn btn-success' %> +
\ No newline at end of file diff --git a/app/views/sessions/new_api_key.html.erb b/app/views/sessions/new_api_key.html.erb new file mode 100644 index 0000000..d79cb95 --- /dev/null +++ b/app/views/sessions/new_api_key.html.erb @@ -0,0 +1,24 @@ +
+

+ Pivotal Tracker +

+
+ You only need to set your API key once (or if you have had to change your password).
+ This is only required for Google OAuth users. Regular users with passwords do not need to fill this form out. Proceed to the Login screen as normal to sign in.

+ For information on how to access your API key, <%= link_to "click this link", "https://www.pivotaltracker.com/help/articles/api_token/" %> . +
+
+ + <%= form_tag set_api_key_path, as: :session do %> + +
+ <%= text_field_tag :username, nil, class: 'form-control', placeholder: 'Email / Username' %> +
+ +
+ <%= password_field_tag :api_key, nil, class: 'form-control', placeholder: 'API Key' %> +
+ + <%= submit_tag t('setapikey').upcase, class: 'btn btn-success' %> + <% end %> +
\ No newline at end of file diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 0000000..d80cc07 --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,9 @@ +require 'json' + +OmniAuth.config.logger = Rails.logger +google_data = JSON.parse(File.read('credentials_oauth.json'))["web"] + + +Rails.application.config.middleware.use OmniAuth::Builder do + provider :google_oauth2, google_data["client_id"], google_data["client_secret"]#, {client_options: {ssl: {ca_file: Rails.root.join("cacert.pem").to_s}}} +end \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 113e6c0..0f267f6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -11,6 +11,7 @@ en: reveal: "Reveal" save: "Save" signin: "Sign In" + setapikey: "Set API Key" signout: "Sign Out" story: "Story" unestimated: "Unestimated" @@ -45,6 +46,9 @@ en: signin: "You will need to sign in first" success: "You have successfully logged in" unauthorized: "No API Key / Token found. Please re-login!" + token_set: "Your API Key has been successfully set!" + token_not_set: "Your API Key wasn't set. Please try again." + without_token: "You're using an account without an API token set. Please check your account or make sure you have an API token set" stories: update: "Story ID#%{id} has been updated successfully" \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 43e6bad..36624e0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,12 +1,17 @@ Rails.application.routes.draw do root to: 'dashboard#index' + get 'auth/:provider/callback', to: 'sessions#google_oauth_login' + get 'auth/failure', to: 'sessions#new' + get 'favicon.ico', controller: :application, action: :favicon controller :sessions do get :login, action: :new post :login, action: :create delete :logout, action: :destroy + get :new_api_key, action: :new_api_key + post :new_api_key, action: :set_api_key, as: :set_api_key end resources :dashboard, only: [:index] diff --git a/db/development b/db/development index 5de3fb3b09f001f5102f0823a0e2f4893366dfcb..f251d365a2f0f854de44bce87cfb6ce924c82924 100644 GIT binary patch delta 16355 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 delta 846 zcmcgpOK4L;6uo!u%ge`CDzQ?eeR=k$-5A^y1qBsE`cddYy6V^Xzyze47wf_lQbCDO z&^X8_xRAu9i_{0rPPz~aZmb`q4I&}X(nWD0X+go(d7~6{=7Ry~iYpb|z-dKcc`1HZ6-VzzyxX+hQPdZ|hevG!OKSmT8^lWwA)Pet+K% zjm+Ssb!(*@|S-WCWzsS1(kaZl|mbGomT7Ssuxr|1$s%(=^4ArrrAwbU%G*! zWMPk?V#O4m+ApN`?P{b{cIB^z0AYe zUhf@`^VMub1e(zj<{(T`bSKa2fAGjn8NwOSQ9T(06z@)NB9% diff --git a/db/test b/db/test index 5db591cb8cbc1b73b23e3ffc271fb7de02eb6802..37fd136b191d78474c595fde9addebad4ffea013 100644 GIT binary patch delta 21 acmZojXh@hK$;>!2XrhcGh}xL2Kpy~2iw70} delta 21 acmZojXh@hK$;`l8I8nwCL~Tr1pbr2=AqF}C diff --git a/secrets.tar.enc b/secrets.tar.enc index a0a935fe985285b2225de214630309063dcb77b3..38e5305d0103053f52d15da80e5ce5e31a837cc9 100644 GIT binary patch literal 7696 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 K0W+;ON8s^1g#^g} literal 4112 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 OH` "abcd"}) - end + $service.stubs(:insert_event).returns(OpenStruct.new({:id => "abcd"})) + request_options_mock = mock() + $service.stubs(:request_options).returns(request_options_mock) + request_options_mock.stubs(:retries=) + end it "should call on Project#create_hangout" do Project.expects(:create_hangout) diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index d5b6548..56ff330 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -13,7 +13,6 @@ def valid_session end describe 'GET new' do - it 'should redirect to root' do get :new, {}, valid_session expect(response).to redirect_to root_path @@ -75,4 +74,43 @@ def valid_session end end + describe 'GET new_api_key' do + it 'should redirect to root' do + get :new_api_key, {}, valid_session + expect(response).to redirect_to root_path + end + end + + describe 'POST new_api_key' do + let(:params) {{ + 'username' => user.username, + 'api_key' => "abcdtestrandomchars" + }} + + it 'should create a new user if one does not currently exist if successful' do + post :set_api_key, params, valid_session + expect(response).to redirect_to login_path + end + + it 'should not create new user if not successful' do + post :set_api_key, {'username' => "",'api_key' => ""}, valid_session + expect(response).to redirect_to new_api_key_path + end + end + + # How do we even get around this?? + # describe 'GET auth/:provider/callback' do + # it 'should log in a user if we have the API tokens for this user' do + # User.stubs(:authenticate_after_oauth).returns(user) + # get oauth_callback_path("google_oauth"), {}, valid_session + # expect(response).to redirect_to root_path + # end + + # it 'should not log in a user if we do not have API tokens for this user' do + # User.stubs(:authenticate_after_oauth).returns(nil) + # get :google_oauth_login, {}, valid_session + # expect(response).to redirect_to login_path + # end + # end + end \ No newline at end of file diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index af0c149..84db98f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4,7 +4,10 @@ before do Project.delete_all $service = mock("$service") - $service.stubs(:insert_event).returns({:id => "abcd"}) + request_options_mock = mock() + $service.stubs(:request_options).returns(request_options_mock) + request_options_mock.stubs(:retries=) + $service.stubs(:insert_event).returns(OpenStruct.new({:id => "abcd"})) end let(:project_id) { "some_id" } context "#create_hangout" do @@ -13,7 +16,8 @@ Project.create_hangout(:project_id) end it "locks before calling API" do - Project.any_instance.expects(:event_id=).with("LOCKED") + Project.any_instance.expects(:event_id=).at_least(1).with("LOCKED") + Project.any_instance.expects(:event_id=).at_most(1).with("abcd") Project.create_hangout(:project_id) end it "creates new project with event" do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3a5dcc1..844b127 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,7 +2,9 @@ describe User, type: :model do let(:username) { Forgery(:internet).email_address } + let(:username2) { Forgery(:internet).email_address } let(:password) { 'password' } + let(:token) {'COMPLETELY_RANDOM_API_KEY'} let(:params) {{ username: username, @@ -66,5 +68,51 @@ User.any_instance.expects(:save) end end + + context '#set_new_api_key' do + it 'creates new user if one does not exist' do + num_users_before = User.all.size + User.set_new_api_key({'username': username2, 'api_key': token}) + assert_equal num_users_before + 1, User.all.size + end + + it 'updates old user if new API key passed in' do + one_mock = mock + JSON.stubs(:parse).with(one_mock).returns({'api_token' => user.token}) + RestClient::Request.stubs(:execute).returns(one_mock) + User.stubs(:salted).returns(user.salt) + User.create(params) + + num_users_before = User.all.size + User.set_new_api_key({username: username, api_key: token}) + assert_equal num_users_before, User.all.size + end + end + + context '#authenticate_after_oauth' do + it 'returns nil if user token not found' do + # User doesn't exist + assert_nil User.authenticate_after_oauth(username2) + + # User exists but doesn't have non-empty token set + one_mock = mock + JSON.stubs(:parse).with(one_mock).returns({'api_token' => ""}) + RestClient::Request.stubs(:execute).returns(one_mock) + User.stubs(:salted).returns(user.salt) + User.create(params) + + assert_nil User.authenticate_after_oauth(username) + end + + it 'returns the user if user token is found' do + one_mock = mock + JSON.stubs(:parse).with(one_mock).returns({'api_token' => user.token}) + RestClient::Request.stubs(:execute).returns(one_mock) + User.stubs(:salted).returns(user.salt) + u = User.create(params) + + assert_equal User.authenticate_after_oauth(username), u + end + end end end \ No newline at end of file