2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2019-03-07 19:31:04 +08:00
|
|
|
class Auth::GoogleOAuth2Authenticator < Auth::ManagedAuthenticator
|
2024-10-16 10:09:07 +08:00
|
|
|
GROUPS_SCOPE = "https://www.googleapis.com/auth/admin.directory.group.readonly"
|
|
|
|
GROUPS_DOMAIN = "admin.googleapis.com"
|
|
|
|
GROUPS_PATH = "/admin/directory/v1/groups"
|
|
|
|
OAUTH2_BASE_URL = "https://oauth2.googleapis.com"
|
2022-10-13 23:04:42 +08:00
|
|
|
|
2014-05-22 06:19:40 +08:00
|
|
|
def name
|
|
|
|
"google_oauth2"
|
|
|
|
end
|
|
|
|
|
2018-07-23 23:51:57 +08:00
|
|
|
def enabled?
|
|
|
|
SiteSetting.enable_google_oauth2_logins
|
|
|
|
end
|
|
|
|
|
2019-03-07 19:31:04 +08:00
|
|
|
def primary_email_verified?(auth_token)
|
|
|
|
# note, emails that come back from google via omniauth are always valid
|
|
|
|
# this protects against future regressions
|
|
|
|
auth_token[:extra][:raw_info][:email_verified]
|
2014-05-22 06:19:40 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def register_middleware(omniauth)
|
2018-02-23 07:19:36 +08:00
|
|
|
options = {
|
|
|
|
setup:
|
|
|
|
lambda do |env|
|
2024-11-08 11:03:17 +08:00
|
|
|
opts = env["omniauth.strategy"].options
|
|
|
|
opts[:client_id] = SiteSetting.google_oauth2_client_id
|
|
|
|
opts[:client_secret] = SiteSetting.google_oauth2_client_secret
|
2023-01-09 20:10:19 +08:00
|
|
|
|
2019-01-31 18:05:25 +08:00
|
|
|
if (google_oauth2_hd = SiteSetting.google_oauth2_hd).present?
|
2024-11-08 11:03:17 +08:00
|
|
|
opts[:hd] = google_oauth2_hd
|
2019-01-31 18:05:25 +08:00
|
|
|
end
|
2023-01-09 20:10:19 +08:00
|
|
|
|
2019-01-31 18:05:25 +08:00
|
|
|
if (google_oauth2_prompt = SiteSetting.google_oauth2_prompt).present?
|
2024-11-08 11:03:17 +08:00
|
|
|
opts[:prompt] = google_oauth2_prompt.gsub("|", " ")
|
|
|
|
end
|
|
|
|
opts[:client_options][:connection_build] = lambda do |builder|
|
|
|
|
if SiteSetting.google_oauth2_verbose_logging
|
|
|
|
builder.response :logger,
|
|
|
|
Rails.logger,
|
|
|
|
{ bodies: true, formatter: Auth::OauthFaradayFormatter }
|
|
|
|
end
|
|
|
|
builder.request :url_encoded
|
|
|
|
builder.adapter FinalDestination::FaradayAdapter
|
2019-01-31 18:05:25 +08:00
|
|
|
end
|
2020-12-09 17:09:31 +08:00
|
|
|
# All the data we need for the `info` and `credentials` auth hash
|
|
|
|
# are obtained via the user info API, not the JWT. Using and verifying
|
|
|
|
# the JWT can fail due to clock skew, so let's skip it completely.
|
|
|
|
# https://github.com/zquestz/omniauth-google-oauth2/pull/392
|
2024-11-08 11:03:17 +08:00
|
|
|
opts[:skip_jwt] = true
|
2023-01-09 20:10:19 +08:00
|
|
|
end,
|
2018-02-23 07:19:36 +08:00
|
|
|
}
|
2022-10-13 23:04:42 +08:00
|
|
|
omniauth.provider :google_oauth2, options
|
2021-12-09 20:30:27 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def after_authenticate(auth_token, existing_account: nil)
|
2022-10-13 23:04:42 +08:00
|
|
|
groups = provides_groups? ? raw_groups(auth_token.uid) : nil
|
|
|
|
auth_token.extra[:raw_groups] = groups if groups
|
|
|
|
|
2021-12-09 20:30:27 +08:00
|
|
|
result = super
|
2022-10-13 23:04:42 +08:00
|
|
|
|
|
|
|
if groups
|
|
|
|
result.associated_groups =
|
|
|
|
groups.map { |group| group.with_indifferent_access.slice(:id, :name) }
|
2021-12-09 20:30:27 +08:00
|
|
|
end
|
2022-10-13 23:04:42 +08:00
|
|
|
|
2021-12-09 20:30:27 +08:00
|
|
|
result
|
|
|
|
end
|
|
|
|
|
|
|
|
def provides_groups?
|
2022-10-13 23:04:42 +08:00
|
|
|
SiteSetting.google_oauth2_hd.present? && SiteSetting.google_oauth2_hd_groups &&
|
|
|
|
SiteSetting.google_oauth2_hd_groups_service_account_admin_email.present? &&
|
|
|
|
SiteSetting.google_oauth2_hd_groups_service_account_json.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def raw_groups(uid)
|
|
|
|
groups = []
|
|
|
|
page_token = nil
|
|
|
|
groups_url = "https://#{GROUPS_DOMAIN}#{GROUPS_PATH}"
|
|
|
|
client = build_service_account_client
|
|
|
|
return if client.nil?
|
|
|
|
|
|
|
|
loop do
|
|
|
|
params = { userKey: uid }
|
|
|
|
params[:pageToken] = page_token if page_token
|
|
|
|
|
|
|
|
response = client.get(groups_url, params: params, raise_errors: false)
|
|
|
|
|
|
|
|
if response.status == 200
|
|
|
|
response = response.parsed
|
|
|
|
groups.push(*response["groups"])
|
|
|
|
page_token = response["nextPageToken"]
|
|
|
|
break if page_token.nil?
|
|
|
|
else
|
|
|
|
Rails.logger.error(
|
|
|
|
"[Discourse Google OAuth2] failed to retrieve groups for #{uid} - status #{response.status}",
|
|
|
|
)
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
groups
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_service_account_client
|
|
|
|
service_account_info = JSON.parse(SiteSetting.google_oauth2_hd_groups_service_account_json)
|
|
|
|
|
|
|
|
payload = {
|
|
|
|
iss: service_account_info["client_email"],
|
|
|
|
aud: "#{OAUTH2_BASE_URL}/token",
|
|
|
|
scope: GROUPS_SCOPE,
|
|
|
|
iat: Time.now.to_i,
|
|
|
|
exp: Time.now.to_i + 60,
|
|
|
|
sub: SiteSetting.google_oauth2_hd_groups_service_account_admin_email,
|
|
|
|
}
|
|
|
|
headers = { "alg" => "RS256", "typ" => "JWT" }
|
|
|
|
key = OpenSSL::PKey::RSA.new(service_account_info["private_key"])
|
|
|
|
|
|
|
|
encoded_jwt = ::JWT.encode(payload, key, "RS256", headers)
|
2023-01-09 20:10:19 +08:00
|
|
|
|
2022-10-13 23:04:42 +08:00
|
|
|
client =
|
|
|
|
OAuth2::Client.new(
|
|
|
|
SiteSetting.google_oauth2_client_id,
|
|
|
|
SiteSetting.google_oauth2_client_secret,
|
|
|
|
site: OAUTH2_BASE_URL,
|
|
|
|
)
|
2023-01-09 20:10:19 +08:00
|
|
|
|
2022-10-13 23:04:42 +08:00
|
|
|
token_response =
|
|
|
|
client.request(
|
|
|
|
:post,
|
|
|
|
"/token",
|
|
|
|
body: {
|
|
|
|
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
|
|
assertion: encoded_jwt,
|
|
|
|
},
|
|
|
|
raise_errors: false,
|
|
|
|
)
|
|
|
|
|
|
|
|
if token_response.status != 200
|
|
|
|
Rails.logger.error(
|
|
|
|
"[Discourse Google OAuth2] failed to retrieve group fetch token - status #{token_response.status}",
|
|
|
|
)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
OAuth2::AccessToken.from_hash(client, token_response.parsed)
|
2014-05-22 06:19:40 +08:00
|
|
|
end
|
2015-04-25 01:10:43 +08:00
|
|
|
end
|