@@ -65,33 +65,19 @@ def omniauth_callback
6565 expires_at : expires_at
6666 )
6767
68- # Choose the account-resolution path based on the provider.
69- #
70- # The :developer provider is a deliberate masquerade hole that lets a
71- # developer log in *as* an existing user without going through Canvas. It
72- # must never be reachable in production, so we gate it twice: the provider
73- # is only registered in dev/test (config/initializers/omniauth.rb) AND we
74- # re-check developer_login_allowed? here as defense in depth.
7568 developer = auth . provider == 'developer'
76- if developer && !developer_login_allowed?
77- Rails . logger . error ( 'Developer login attempted in a non-permitted environment' )
78- redirect_to root_path , alert : 'Authentication failed. Please try again.'
79- return
80- end
81-
8269 user =
8370 if developer
8471 developer_lookup_or_create ( user_data , access_token )
8572 else
8673 canvas_lookup_or_create ( user_data , access_token )
8774 end
8875
89- # canvas_lookup_or_create returns nil when it refuses to silently re-key an
90- # existing account to a new canvas_uid (potential account takeover ).
76+ # Either path returns nil when it refuses the login (e.g. canvas_lookup
77+ # declining to re-key an existing account to a new canvas_uid).
9178 if user . nil?
9279 redirect_to root_path ,
93- alert : 'We could not link your Canvas account to an existing ' \
94- 'account with the same email. Please contact an administrator.'
80+ alert : 'We could not link your account. Please contact an administrator.'
9581 return
9682 end
9783
@@ -126,39 +112,21 @@ def ensure_developer_test_enrollments(user)
126112 end
127113 end
128114
129- # Whether the developer (masquerade) login path may be used.
130- #
131- # This mirrors the env guard that registers the :developer OmniAuth provider
132- # in config/initializers/omniauth.rb -- both must be true for developer login
133- # to be reachable. Keeping the check here (and not only in the initializer)
134- # ensures the impersonation path can never run in production even if a
135- # :developer callback somehow reaches the controller.
136115 def developer_login_allowed?
137- # Rails.env.local? is true only in development and test, matching the env
138- # guard around the :developer provider in config/initializers/omniauth.rb.
139116 Rails . env . local?
140117 end
141118
142- # Canonical production login path.
143- #
144- # Canvas is the source of truth for identity, so a user is keyed by their
145- # canvas_uid:
146- # * If we recognize the canvas_uid, we refresh the email from Canvas.
147- # * If we DON'T recognize the canvas_uid but the incoming email already
148- # belongs to another account, we REFUSE to re-key that account to the new
149- # canvas_uid and return nil. Silently re-keying would let anyone able to
150- # create a Canvas account with a victim's email take over the victim's
151- # account here.
152- # * Otherwise we create a brand new account.
119+ # Canonical production login path. Canvas owns identity, so users are keyed by
120+ # canvas_uid: we refresh the email on a canvas_uid match and create a new
121+ # account when the canvas_uid is unknown. We return nil (rather than re-keying)
122+ # when a different canvas_uid presents an email that already belongs to an
123+ # account, leaving the existing account untouched.
153124 def canvas_lookup_or_create ( user_data , auth_token )
154125 user = User . find_by ( canvas_uid : user_data [ 'id' ] )
155126
156127 if user
157128 user . email = user_data [ 'email' ]
158129 elsif User . exists? ( email : user_data [ 'primary_email' ] )
159- # A different canvas_uid is presenting an email we already know. Refuse
160- # the silent account re-key (potential takeover) and let the caller
161- # surface an error to the user.
162130 Rails . logger . warn (
163131 "Refusing to link canvas_uid=#{ user_data [ 'id' ] } to existing account " \
164132 "with email=#{ user_data [ 'primary_email' ] } "
@@ -175,15 +143,12 @@ def canvas_lookup_or_create(user_data, auth_token)
175143 persist_login! ( user , auth_token )
176144 end
177145
178- # Developer / test masquerade path.
179- #
180- # This INTENTIONALLY preserves the legacy email-first matching so a developer
181- # can log in *as* an existing user without going through Canvas. It is a
182- # deliberate impersonation hole and is only ever reached when
183- # developer_login_allowed? is true (see omniauth_callback and omniauth.rb).
184- # Do NOT use this logic for the production Canvas path -- see
185- # canvas_lookup_or_create for why email-first matching is unsafe there.
146+ # Developer / test masquerade path. INTENTIONALLY matches an existing user by
147+ # email so a developer can log in *as* them without Canvas. Gated to dev/test
148+ # only; returns nil otherwise. Do not use this matching for the Canvas path.
186149 def developer_lookup_or_create ( user_data , auth_token )
150+ return nil unless developer_login_allowed?
151+
187152 if User . exists? ( email : user_data [ 'primary_email' ] )
188153 user = User . find_by ( email : user_data [ 'primary_email' ] )
189154 user . canvas_uid = user_data [ 'id' ]
0 commit comments