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.



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

def initialize(secure_session:)
  @secure_session = secure_session
end

Class Method Details

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



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

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



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

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

.sso_secretObject



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

def self.sso_secret
  SiteSetting.discourse_connect_secret
end

.sso_urlObject



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

def self.sso_url
  SiteSetting.discourse_connect_url
end

Instance Method Details

#expire_nonce!Object



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

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:



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

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



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

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



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

def nonce_key
  "SSO_NONCE_#{nonce}"
end

#nonce_valid?Boolean

Returns:

  • (Boolean)


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

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



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

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



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

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

#used_nonce_keyObject



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

def used_nonce_key
  "USED_SSO_NONCE_#{nonce}"
end