Skip to content

Commit f44a2cd

Browse files
committed
DEV: Refactor managed_authenticator into its own file
1 parent b3124f9 commit f44a2cd

File tree

4 files changed

+108
-47
lines changed

4 files changed

+108
-47
lines changed

config/locales/server.en.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ en:
66
openid_connect_client_secret: "OpenID Connect client secret"
77
openid_connect_authorize_scope: "The scopes sent to the authorize endpoint. This must include 'openid'."
88
openid_connect_token_scope: "The scopes sent when requesting the token endpoint. The official specification does not require this."
9-
openid_connect_error_redirects: "If the callback error_reason contains the first parameter, the user will be redirected to the URL in the second parameter"
9+
openid_connect_error_redirects: "If the callback error_reason contains the first parameter, the user will be redirected to the URL in the second parameter"
10+
openid_connect_allow_association_change: "Allow users to disconnect and reconnect their Discourse accounts from the OpenID Connect provider"

config/settings.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ plugins:
77
default: ""
88
openid_connect_client_secret:
99
default: ""
10+
openid_connect_allow_association_change:
11+
default: false
1012
openid_connect_authorize_scope:
1113
default: "openid"
1214
openid_connect_token_scope:

lib/managed_authenticator.rb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
class Auth::ManagedAuthenticator < Auth::Authenticator
2+
def description_for_user(user)
3+
info = UserAssociatedAccount.find_by(provider_name: name, user_id: user.id).info
4+
info["name"] || info["email"] || info["nickname"] || ""
5+
end
6+
7+
# These three methods are designed to be overriden by child classes
8+
def match_by_email
9+
true
10+
end
11+
12+
def can_revoke?
13+
true
14+
end
15+
16+
def can_connect_existing_user?
17+
true
18+
end
19+
20+
def revoke(user, skip_remote: false)
21+
association = UserAssociatedAccount.find_by(provider_name: name, user_id: user.id)
22+
raise Discourse::NotFound if association.nil?
23+
association.destroy!
24+
true
25+
end
26+
27+
def after_authenticate(auth_token, existing_account: nil)
28+
result = Auth::Result.new
29+
30+
# Store all the metadata for later, in case the `after_create_account` hook is used
31+
result.extra_data = {
32+
provider: auth_token[:provider],
33+
uid: auth_token[:uid],
34+
info: auth_token[:info],
35+
extra: auth_token[:extra],
36+
credentials: auth_token[:credentials]
37+
}
38+
39+
# Build the Auth::Result object
40+
info = auth_token[:info]
41+
result.email = email = info[:email]
42+
result.name = name = "#{info[:first_name]} #{info[:last_name]}"
43+
result.username = info[:nickname]
44+
45+
# Try and find an association for this account
46+
association = UserAssociatedAccount.find_by(provider_name: auth_token[:provider], provider_uid: auth_token[:uid])
47+
result.user = association&.user
48+
49+
# Reconnecting to existing account
50+
if can_connect_existing_user? && existing_account && (association.nil? || existing_account.id != association.user_id)
51+
association.destroy! if association
52+
association = nil
53+
result.user = existing_account
54+
end
55+
56+
# Matching an account by email
57+
if match_by_email && association.nil? && (user = User.find_by_email(email))
58+
UserAssociatedAccount.where(user: user, provider_name: auth_token[:provider]).destroy_all # Destroy existing associations for the new user
59+
result.user = user
60+
end
61+
62+
# Add the association to the database if it doesn't already exist
63+
if association.nil? && result.user
64+
association = create_association!(result.extra_data.merge(user: result.user))
65+
end
66+
67+
# Update all the metadata in the association:
68+
if association
69+
association.update!(
70+
info: auth_token[:info],
71+
credentials: auth_token[:credentials],
72+
extra: auth_token[:extra]
73+
)
74+
end
75+
76+
result.email_valid = true
77+
78+
result
79+
end
80+
81+
def create_association!(hash)
82+
association = UserAssociatedAccount.create!(
83+
user: hash[:user],
84+
provider_name: hash[:provider],
85+
provider_uid: hash[:uid],
86+
info: hash[:info],
87+
credentials: hash[:credentials],
88+
extra: hash[:extra]
89+
)
90+
end
91+
92+
def after_create_account(user, auth)
93+
data = auth[:extra_data]
94+
create_association!(data.merge(user: user))
95+
end
96+
end

plugin.rb

Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,20 @@
66

77
require_relative "lib/omniauth_open_id_connect"
88
require_relative 'app/models/user_associated_account'
9+
require_relative "lib/managed_authenticator"
910

10-
class Auth::ManagedAuthenticator < Auth::Authenticator
11-
def match_by_email
12-
true
13-
end
14-
15-
def after_authenticate(auth_token)
16-
# puts "after authenticate ", auth_token.to_json
17-
result = Auth::Result.new
18-
19-
result.extra_data = {
20-
provider: auth_token[:provider],
21-
uid: auth_token[:uid],
22-
info: auth_token[:info],
23-
extra: auth_token[:extra],
24-
credentials: auth_token[:credentials]
25-
}
26-
27-
info = auth_token[:info]
28-
result.email = email = info[:email]
29-
result.name = name = "#{info[:first_name]} #{info[:last_name]}"
30-
result.username = info[:nickname]
31-
32-
association = UserAssociatedAccount.find_by(provider_name: auth_token[:provider], provider_uid: auth_token[:uid])
33-
34-
if match_by_email && association.nil? && (user = User.find_by_email(email)) && !UserAssociatedAccount.exists?(user: user, provider_name: auth_token[:provider])
35-
association = UserAssociatedAccount.create!(user: user, provider_name: auth_token[:provider], provider_uid: auth_token[:uid], info: auth_token[:info], credentials: auth_token[:credentials], extra: auth_token[:extra])
36-
end
37-
38-
result.user = association&.user
39-
result.email_valid = true
11+
class OpenIDConnectAuthenticator < Auth::ManagedAuthenticator
4012

41-
result
13+
def name
14+
'oidc'
4215
end
4316

44-
def after_create_account(user, auth)
45-
data = auth[:extra_data]
46-
association = UserAssociatedAccount.create!(
47-
user: user,
48-
provider_name: data[:provider],
49-
provider_uid: data[:uid],
50-
info: data[:info],
51-
credentials: data[:credentials],
52-
extra: data[:extra]
53-
)
17+
def can_revoke?
18+
SiteSetting.openid_connect_allow_association_change
5419
end
55-
end
5620

57-
class OpenIDConnectAuthenticator < Auth::ManagedAuthenticator
58-
59-
def name
60-
'oidc'
21+
def can_connect_existing_user?
22+
SiteSetting.openid_connect_allow_association_change
6123
end
6224

6325
def enabled?

0 commit comments

Comments
 (0)