diff --git a/Gemfile b/Gemfile index 4490ae79379..70b240b22b1 100644 --- a/Gemfile +++ b/Gemfile @@ -129,6 +129,7 @@ gem 'omniauth-facebook' gem 'omniauth-twitter' gem 'omniauth-github' gem 'omniauth-oauth2', require: false +gem 'omniauth-google-oauth2' gem 'oj' # while resolving https://groups.google.com/forum/#!topic/ruby-pg/5_ylGmog1S4 gem 'pg', '0.15.1' diff --git a/Gemfile.lock b/Gemfile.lock index 06f20e0de59..1db7969fb28 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -197,6 +197,9 @@ GEM omniauth-github (1.1.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) + omniauth-google-oauth2 (0.2.4) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.1) omniauth-oauth (1.0.1) oauth omniauth (~> 1.0) @@ -436,6 +439,7 @@ DEPENDENCIES omniauth omniauth-facebook omniauth-github + omniauth-google-oauth2 omniauth-oauth2 omniauth-openid omniauth-twitter diff --git a/app/assets/javascripts/discourse/models/login_method.js b/app/assets/javascripts/discourse/models/login_method.js index 3a7aabd8c6f..e969fefb0ea 100644 --- a/app/assets/javascripts/discourse/models/login_method.js +++ b/app/assets/javascripts/discourse/models/login_method.js @@ -31,6 +31,7 @@ Discourse.LoginMethod.reopenClass({ * */ [ "google", + "google_oauth2", "facebook", "cas", "twitter", @@ -41,7 +42,7 @@ Discourse.LoginMethod.reopenClass({ var params = {name: name}; - if (name === "google") { + if (name === "google" || name === "google_oauth2") { params.frameWidth = 850; params.frameHeight = 500; } else if (name === "facebook") { diff --git a/app/assets/stylesheets/common/components/buttons.css.scss b/app/assets/stylesheets/common/components/buttons.css.scss index 5e2a4e3da66..40dc0e2f66d 100644 --- a/app/assets/stylesheets/common/components/buttons.css.scss +++ b/app/assets/stylesheets/common/components/buttons.css.scss @@ -125,7 +125,7 @@ font-family: zocial; line-height: 0.9; } - &.google { + &.google, &.google_oauth2 { background: $google; &:before { content: "G"; diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index ad5c2be4db4..e59a96279d0 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -8,6 +8,7 @@ class Users::OmniauthCallbacksController < ApplicationController BUILTIN_AUTH = [ Auth::FacebookAuthenticator.new, Auth::OpenIdAuthenticator.new("google", "https://www.google.com/accounts/o8/id", trusted: true), + Auth::GoogleOAuth2Authenticator.new, Auth::OpenIdAuthenticator.new("yahoo", "https://me.yahoo.com", trusted: true), Auth::GithubAuthenticator.new, Auth::TwitterAuthenticator.new diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index e5d82813ba2..f817326ebf8 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -27,6 +27,9 @@ class AdminDashboardData gc_checks, sidekiq_check || queue_size_check, ram_check, + old_google_config_check, + both_googles_config_check, + google_oauth2_config_check, facebook_config_check, twitter_config_check, github_config_check, @@ -45,6 +48,7 @@ class AdminDashboardData ].compact end + def self.fetch_stats AdminDashboardData.new end @@ -106,8 +110,20 @@ class AdminDashboardData I18n.t('dashboard.memory_warning') if MemInfo.new.mem_total and MemInfo.new.mem_total < 1_000_000 end + def old_google_config_check + I18n.t('dashboard.enable_google_logins_warning') if SiteSetting.enable_google_logins + end + + def both_googles_config_check + I18n.t('dashboard.both_googles_warning') if SiteSetting.enable_google_logins && SiteSetting.enable_google_oauth2_logins + end + + def google_oauth2_config_check + I18n.t('dashboard.google_oauth2_config_warning') if SiteSetting.enable_google_logins && (SiteSetting.google_oauth2_client_id.blank? || SiteSetting.google_oauth2_client_secret.blank?) + end + def facebook_config_check - I18n.t('dashboard.facebook_config_warning') if SiteSetting.enable_facebook_logins and (SiteSetting.facebook_app_id.blank? or SiteSetting.facebook_app_secret.blank?) + I18n.t('dashboard.facebook_config_warning') if SiteSetting.enable_facebook_logins && (SiteSetting.facebook_app_id.blank? || SiteSetting.facebook_app_secret.blank?) end def twitter_config_check diff --git a/app/models/google_user_info.rb b/app/models/google_user_info.rb new file mode 100644 index 00000000000..f48416dda46 --- /dev/null +++ b/app/models/google_user_info.rb @@ -0,0 +1,3 @@ +class GoogleUserInfo < ActiveRecord::Base + belongs_to :user +end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 67301565cc3..e985e7b7401 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -509,6 +509,9 @@ en: google: title: "with Google" message: "Authenticating with Google (make sure pop up blockers are not enabled)" + google_oauth2: + title: "with Google" + message: "Authenticating with Google (make sure pop up blockers are not enabled)" twitter: title: "with Twitter" message: "Authenticating with Twitter (make sure pop up blockers are not enabled)" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index b725ea74df2..a736d4a94ae 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -518,6 +518,9 @@ en: sidekiq_warning: 'Sidekiq is not running. Many tasks, like sending emails, are executed asynchronously by sidekiq. Please ensure at least one sidekiq process is running. Learn about Sidekiq here.' queue_size_warning: 'The number of queued jobs is %{queue_size}, which is high. This could indicate a problem with the Sidekiq process(es), or you may need to add more Sidekiq workers.' memory_warning: 'Your server is running with less than 1 GB of total memory. At least 1 GB of memory is recommended.' + enable_google_logins_warning: "You are using a deprecated version of Google's OpenID authentication. Google will be ending support for OpenID by April 20, 2015. Start using Google Oauth2 as soon as possible." + both_googles_warning: "You have both enable_google_logins and enable_google_oauth2_logins checked in the site settings. Disable enable_google_logins." + google_oauth2_config_warning: 'The server is configured to allow signup and log in with Google Oauth2 (enable_google_oauth2_logins), but the client id and client secret values are not set. Go to the Site Settings and update the settings.' facebook_config_warning: 'The server is configured to allow signup and log in with Facebook (enable_facebook_logins), but the app id and app secret values are not set. Go to the Site Settings and update the settings. See this guide to learn more.' twitter_config_warning: 'The server is configured to allow signup and log in with Twitter (enable_twitter_logins), but the key and secret values are not set. Go to the Site Settings and update the settings. See this guide to learn more.' github_config_warning: 'The server is configured to allow signup and log in with GitHub (enable_github_logins), but the client id and secret values are not set. Go to the Site Settings and update the settings. See this guide to learn more.' @@ -720,9 +723,13 @@ en: sso_overrides_name: "Overrides local name with external site name from SSO payload (WARNING: discrepancies can occur due to normalization of local names)" enable_local_logins: "Enable traditional, local username and password authentication" - enable_google_logins: "Enable Google authentication" + enable_google_logins: "(deprecated) Enable Google authentication. This is the OpenID method of authentication which Google has deprecated. New installs will NOT work with this. Use Google Oauth2 instead. Existing installs must move to Google Oauth2 by April 20, 2015." enable_yahoo_logins: "Enable Yahoo authentication" + enable_google_oauth2_logins: "Enable Google Oauth2 authentication. This is the method of authentication that Google currently supports. Requires key and secret." + google_oauth2_client_id: "Client ID of your Google application." + google_oauth2_client_secret: "Client secret of your Google application." + enable_twitter_logins: "Enable Twitter authentication, requires twitter_consumer_key and twitter_consumer_secret" twitter_consumer_key: "Consumer key for Twitter authentication, registered at http://dev.twitter.com" twitter_consumer_secret: "Consumer secret for Twitter authentication, registered at http://dev.twitter.com" diff --git a/config/site_settings.yml b/config/site_settings.yml index 75f2ca081ef..9f7df9c150e 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -114,8 +114,13 @@ users: default: 8 block_common_passwords: true enable_google_logins: + client: true + default: false + enable_google_oauth2_logins: client: true default: true + google_oauth2_client_id: '' + google_oauth2_client_secret: '' enable_yahoo_logins: client: true default: true diff --git a/db/migrate/20140521192142_create_google_user_infos.rb b/db/migrate/20140521192142_create_google_user_infos.rb new file mode 100644 index 00000000000..28da7b547c6 --- /dev/null +++ b/db/migrate/20140521192142_create_google_user_infos.rb @@ -0,0 +1,20 @@ +class CreateGoogleUserInfos < ActiveRecord::Migration + def change + create_table :google_user_infos do |t| + t.integer :user_id, null: false + t.string :google_user_id, null: false + t.string :first_name + t.string :last_name + t.string :email + t.string :gender + t.string :name + t.string :link + t.string :profile_link + t.string :picture + + t.timestamps + end + add_index :google_user_infos, :user_id, unique: true + add_index :google_user_infos, :google_user_id, unique: true + end +end diff --git a/db/migrate/20140521220115_google_openid_default_has_changed.rb b/db/migrate/20140521220115_google_openid_default_has_changed.rb new file mode 100644 index 00000000000..3ee8ac3c300 --- /dev/null +++ b/db/migrate/20140521220115_google_openid_default_has_changed.rb @@ -0,0 +1,13 @@ +class GoogleOpenidDefaultHasChanged < ActiveRecord::Migration + def up + result = Category.exec_sql("SELECT count(*) FROM site_settings WHERE name = 'enable_google_logins'") + if result[0]['count'].to_i == 0 + # The old default was true, so add a row to keep it that way. + execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('enable_google_logins', 5, 't', now(), now())" + end + end + + def down + # No need to undo. + end +end diff --git a/lib/auth.rb b/lib/auth.rb index 3acb5891a12..a218c43f28c 100644 --- a/lib/auth.rb +++ b/lib/auth.rb @@ -6,3 +6,4 @@ require_dependency 'auth/facebook_authenticator' require_dependency 'auth/open_id_authenticator' require_dependency 'auth/github_authenticator' require_dependency 'auth/twitter_authenticator' +require_dependency 'auth/google_oauth2_authenticator' diff --git a/lib/auth/google_oauth2_authenticator.rb b/lib/auth/google_oauth2_authenticator.rb new file mode 100644 index 00000000000..63158e4ce7d --- /dev/null +++ b/lib/auth/google_oauth2_authenticator.rb @@ -0,0 +1,67 @@ +class Auth::GoogleOAuth2Authenticator < Auth::Authenticator + + def name + "google_oauth2" + end + + def after_authenticate(auth_hash) + session_info = parse_hash(auth_hash) + google_hash = session_info[:google] + + result = Auth::Result.new + result.email = session_info[:email] + result.email_valid = session_info[:email_valid] + result.name = session_info[:name] + + result.extra_data = google_hash + + user_info = GoogleUserInfo.find_by(google_user_id: google_hash[:google_user_id]) + result.user = user_info.try(:user) + + if !result.user && !result.email.blank? && result.user = User.find_by(email: Email.downcase(result.email)) + GoogleUserInfo.create({user_id: result.user.id}.merge(google_hash)) + end + + result + end + + def after_create_account(user, auth) + data = auth[:extra_data] + GoogleUserInfo.create({user_id: user.id}.merge(data)) + end + + def register_middleware(omniauth) + omniauth.provider :google_oauth2, + :setup => lambda { |env| + strategy = env["omniauth.strategy"] + strategy.options[:client_id] = SiteSetting.google_oauth2_client_id + strategy.options[:client_secret] = SiteSetting.google_oauth2_client_secret + } + end + + protected + + def parse_hash(hash) + extra = hash[:extra][:raw_info] + + h = {} + + h[:email] = hash[:info][:email] + h[:name] = hash[:info][:name] + h[:email_valid] = hash[:extra][:raw_info][:email_verified] + + h[:google] = { + google_user_id: hash[:uid] || extra[:sub], + email: extra[:email], + first_name: extra[:given_name], + last_name: extra[:family_name], + gender: extra[:gender], + name: extra[:name], + link: extra[:hd], + profile_link: extra[:profile], + picture: extra[:picture] + } + + h + end +end \ No newline at end of file diff --git a/spec/components/auth/google_oauth2_authenticator_spec.rb b/spec/components/auth/google_oauth2_authenticator_spec.rb new file mode 100644 index 00000000000..adab781c2fa --- /dev/null +++ b/spec/components/auth/google_oauth2_authenticator_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +# For autospec: +Auth.send(:remove_const, :GoogleOAuth2Authenticator) +load 'auth/google_oauth2_authenticator.rb' + +describe Auth::GoogleOAuth2Authenticator do + + context 'after_authenticate' do + it 'can authenticate and create a user record for already existing users' do + authenticator = described_class.new + user = Fabricate(:user) + + hash = { + :uid => "123456789", + :info => { + :name => "John Doe", + :email => user.email + }, + :extra => { + :raw_info => { + :email => "user@domain.example.com", + :email_verified => true, + :name => "John Doe" + } + } + } + + result = authenticator.after_authenticate(hash) + + result.user.id.should == user.id + end + + it 'can create a proper result for non existing users' do + hash = { + :uid => "123456789", + :info => { + :name => "Jane Doe", + :email => "jane.doe@the.google.com" + }, + :extra => { + :raw_info => { + :email => "jane.doe@the.google.com", + :email_verified => true, + :name => "Jane Doe" + } + } + } + + authenticator = described_class.new + result = authenticator.after_authenticate(hash) + + result.user.should be_nil + result.extra_data[:name].should == "Jane Doe" + end + end + +end