Class: DiscourseConnect

Inherits:
DiscourseConnectBase show all
Defined in:
app/models/discourse_connect.rb

Defined Under Namespace

Classes: BannedExternalId, BlankExternalId

Constant Summary collapse

BANNED_EXTERNAL_IDS =
%w[none nil blank null]

Constants inherited from DiscourseConnectBase

DiscourseConnectBase::ACCESSORS, DiscourseConnectBase::BOOLS, DiscourseConnectBase::FIXNUMS

Instance Attribute Summary

Attributes inherited from DiscourseConnectBase

#sso_secret, #sso_url

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from DiscourseConnectBase

#custom_fields, #diagnostics, nonce_expiry_time, nonce_expiry_time=, parse, #payload, sign, #sign, #to_h, #to_json, #to_url, #unsigned_payload, used_nonce_expiry_time

Constructor Details

#initialize(secure_session:) ⇒ DiscourseConnect

Returns a new instance of DiscourseConnect.



29
30
31
# File 'app/models/discourse_connect.rb', line 29

def initialize(secure_session:)
  @secure_session = secure_session
end

Class Method Details

.generate_sso(return_path = "/", secure_session:) ⇒ Object



17
18
19
20
21
22
23
# File 'app/models/discourse_connect.rb', line 17

def self.generate_sso(return_path = "/", secure_session:)
  sso = new(secure_session: secure_session)
  sso.nonce = SecureRandom.hex
  sso.register_nonce(return_path)
  sso.return_sso_url = Discourse.base_url + "/session/sso_login"
  sso
end

.generate_url(return_path = "/", secure_session:) ⇒ Object



25
26
27
# File 'app/models/discourse_connect.rb', line 25

def self.generate_url(return_path = "/", secure_session:)
  generate_sso(return_path, secure_session: secure_session).to_url
end

.sso_secretObject



13
14
15
# File 'app/models/discourse_connect.rb', line 13

def self.sso_secret
  SiteSetting.discourse_connect_secret
end

.sso_urlObject



9
10
11
# File 'app/models/discourse_connect.rb', line 9

def self.sso_url
  SiteSetting.discourse_connect_url
end

Instance Method Details

#expire_nonce!Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/models/discourse_connect.rb', line 73

def expire_nonce!
  if nonce
    if SiteSetting.discourse_connect_csrf_protection
      @secure_session[nonce_key] = nil
    else
      Discourse.cache.delete nonce_key
    end

    Discourse.cache.write(
      used_nonce_key,
      return_path,
      expires_in: DiscourseConnectBase.used_nonce_expiry_time,
    )
  end
end

#lookup_or_create_user(ip_address = nil) ⇒ Object

Raises:



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'app/models/discourse_connect.rb', line 99

def lookup_or_create_user(ip_address = nil)
  # we don't want to ban 0 from being an external id
  external_id = self.external_id.to_s

  raise BlankExternalId if external_id.blank?

  raise BannedExternalId, external_id if BANNED_EXTERNAL_IDS.include?(external_id.downcase)

  # we protect here to ensure there is no situation where the same external id
  # concurrently attempts to create or update sso records
  #
  # we can get duplicate HTTP requests quite easily (client rapid refresh) and this path does stuff such
  # as updating groups for a users and so on that can happen even after the sso record and user is there
  DistributedMutex.synchronize("sso_lookup_or_create_user_#{external_id}") do
    lookup_or_create_user_unsafe(ip_address)
  end
end

#nonce_errorObject



55
56
57
58
59
60
61
62
63
# File 'app/models/discourse_connect.rb', line 55

def nonce_error
  if Discourse.cache.read(used_nonce_key).present?
    "Nonce has already been used"
  elsif SiteSetting.discourse_connect_csrf_protection
    "Nonce is incorrect, was generated in a different browser session, or has expired"
  else
    "Nonce is incorrect, or has expired"
  end
end

#nonce_keyObject



89
90
91
# File 'app/models/discourse_connect.rb', line 89

def nonce_key
  "SSO_NONCE_#{nonce}"
end

#nonce_valid?Boolean

Returns:

  • (Boolean)


47
48
49
50
51
52
53
# File 'app/models/discourse_connect.rb', line 47

def nonce_valid?
  if SiteSetting.discourse_connect_csrf_protection
    nonce && @secure_session[nonce_key].present?
  else
    nonce && Discourse.cache.read(nonce_key).present?
  end
end

#register_nonce(return_path) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/models/discourse_connect.rb', line 33

def register_nonce(return_path)
  if nonce
    if SiteSetting.discourse_connect_csrf_protection
      @secure_session.set(nonce_key, return_path, expires: DiscourseConnectBase.nonce_expiry_time)
    else
      Discourse.cache.write(
        nonce_key,
        return_path,
        expires_in: DiscourseConnectBase.nonce_expiry_time,
      )
    end
  end
end

#return_pathObject



65
66
67
68
69
70
71
# File 'app/models/discourse_connect.rb', line 65

def return_path
  if SiteSetting.discourse_connect_csrf_protection
    @secure_session[nonce_key] || "/"
  else
    Discourse.cache.read(nonce_key) || "/"
  end
end

#used_nonce_keyObject



93
94
95
# File 'app/models/discourse_connect.rb', line 93

def used_nonce_key
  "USED_SSO_NONCE_#{nonce}"
end