|
1
|
================================================================================================
|
|
2
|
PROPOSAL - [MINOR] functional - p2j.sso_reauth.js handleSsoReauth
|
|
3
|
The expiry reload terminates the session it claims to re-attach to
|
|
4
|
================================================================================================
|
|
5
|
|
|
6
|
REVIEW FINDING (abridged)
|
|
7
|
"The countdown-expiry window.location.reload() cannot 're-attach to a session the server
|
|
8
|
re-authenticated' in the GUI web client [...] window.onpagehide [...] writes
|
|
9
|
{exitTheApplication:true, date:now} to sessionStorage; on the reloaded page handleOpenEvent calls
|
|
10
|
isRequiredToRedirect(), true for the whole watchdogTimeout window [...] and runs
|
|
11
|
sendNotification(types.MSG_QUIT); doRedirectToLogoutPage();."
|
|
12
|
|
|
13
|
FILES
|
|
14
|
src/com/goldencode/p2j/ui/client/driver/web/res/p2j.sso_reauth.js
|
|
15
|
src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js
|
|
16
|
|
|
17
|
------------------------------------------------------------------------------------------------
|
|
18
|
1. ROOT CAUSE
|
|
19
|
------------------------------------------------------------------------------------------------
|
|
20
|
|
|
21
|
p2j.socket.js maintains an invariant that predates this change: in the GUI client, a page unload
|
|
22
|
that is not an explicit logout means "the user is closing or reloading the application", and the
|
|
23
|
next page to open within watchdogTimeout must quit the session rather than adopt it. That is
|
|
24
|
implemented by window.onpagehide (p2j.socket.js:6861) writing the exitTheApplication object to
|
|
25
|
sessionStorage, and isRequiredToRedirect() (:6419) reading it back:
|
|
26
|
|
|
27
|
if (exit && (date < (exit.date + watchdogTimeout))) // 120000 ms configured, 40 min in debug
|
|
28
|
{
|
|
29
|
return true;
|
|
30
|
}
|
|
31
|
|
|
32
|
handleOpenEvent then sends MSG_QUIT and calls doRedirectToLogoutPage().
|
|
33
|
|
|
34
|
The new expiry branch calls window.location.reload() without opting out of that invariant, so it
|
|
35
|
inherits it: the reload is indistinguishable from a user-initiated one. The guard added at the top
|
|
36
|
of handleOpenEvent does not help, because exitTheApplication is a plain module variable that is a
|
|
37
|
fresh false on the reloaded document - the surviving state is the sessionStorage object, not the
|
|
38
|
variable.
|
|
39
|
|
|
40
|
The state this exists to rescue is genuinely reachable: SsoTokenManager.processCallbackReauthentication
|
|
41
|
clears reauthInProgress and cancels reauthTimeoutFuture *before* the callback page is emitted, and
|
|
42
|
VirtualDesktopWebHandler's notification is best-effort -
|
|
43
|
try{window.opener.postMessage(...)}catch(e){} followed by window.close() - so a COOP header or an
|
|
44
|
early popup close loses it while the session is alive. The grace period does not cover that case
|
|
45
|
either: the callback page's window.close() makes reauthPopup.closed true and
|
|
46
|
popupCloseCheckInterval nulls reauthMessageListener, so reauthInFlight() is false and the reload
|
|
47
|
fires immediately.
|
|
48
|
|
|
49
|
Severity is MINOR because the deleted showSessionExpiredPage was no better here - it wiped the DOM
|
|
50
|
but left the socket open, orphaning the re-authenticated session. The user lost it either way. What
|
|
51
|
is new is a comment and javadoc asserting a recovery the code cannot perform.
|
|
52
|
|
|
53
|
------------------------------------------------------------------------------------------------
|
|
54
|
2. PROPOSED FIX
|
|
55
|
------------------------------------------------------------------------------------------------
|
|
56
|
|
|
57
|
Two options. 2.1 makes the code match its comment and is the smaller change; 2.2 is the honest
|
|
58
|
long-term design. Doing neither and only correcting the javadoc (2.3) is an acceptable minimum.
|
|
59
|
|
|
60
|
2.1 Opt this one reload out of the quit-on-reload invariant
|
|
61
|
-------------------------------------------------------------
|
|
62
|
|
|
63
|
Add an explicit, narrowly scoped signal to p2j.socket, beside the other exported helpers:
|
|
64
|
|
|
65
|
+ /**
|
|
66
|
+ * Mark the imminent page unload as an internal reload rather than the user leaving, so the
|
|
67
|
+ * reloaded page adopts the existing session instead of quitting it.
|
|
68
|
+ * <p>
|
|
69
|
+ * Used by the SSO re-auth overlay, whose expiry reload exists precisely to re-attach to a
|
|
70
|
+ * session the server may have re-authenticated. Also suppresses the leave-confirmation, which
|
|
71
|
+ * would otherwise prompt the user about a reload they did not ask for.
|
|
72
|
+ */
|
|
73
|
+ me.beginInternalReload = function()
|
|
74
|
+ {
|
|
75
|
+ internalReload = true;
|
|
76
|
+ };
|
|
77
|
|
|
78
|
with a module variable
|
|
79
|
|
|
80
|
+ /** True while an internally requested reload is in flight; see me.beginInternalReload. */
|
|
81
|
+ var internalReload = false;
|
|
82
|
|
|
83
|
and honoured in onpagehide (:6861):
|
|
84
|
|
|
85
|
if (!exitTheApplication)
|
|
86
|
{
|
|
87
|
+ if (internalReload)
|
|
88
|
+ {
|
|
89
|
+ // an internal reload re-attaches to the same session; do not arm the quit-on-reload
|
|
90
|
+ // path that a user-initiated reload or a tab close arms
|
|
91
|
+ return;
|
|
92
|
+ }
|
|
93
|
// close or reload cases
|
|
94
|
exitTheApplication = true;
|
|
95
|
p2j.saveObject("exitTheApplication", { ... }, false);
|
|
96
|
}
|
|
97
|
|
|
98
|
and in the expiry branch of p2j.sso_reauth.js, immediately before the reload:
|
|
99
|
|
|
100
|
+ p2j.socket.beginInternalReload();
|
|
101
|
countdownDiv.textContent = 'Confirming sign-in...';
|
|
102
|
window.location.reload();
|
|
103
|
|
|
104
|
The same flag is what suppresses the leave-confirmation - see
|
|
105
|
proposal-issue-minor-functional-3, which owns that half and should land with this one.
|
|
106
|
|
|
107
|
With 2.1 the comment at :247-254 becomes true: a live session re-attaches on the reconnect, a killed
|
|
108
|
one is rejected at the websocket upgrade and lands on the login page.
|
|
109
|
|
|
110
|
2.2 Preferred alternative: ask the server instead of reloading
|
|
111
|
----------------------------------------------------------------
|
|
112
|
|
|
113
|
The socket is still open and the server already knows the answer - the entry's reauthInProgress is
|
|
114
|
false once processCallbackReauthentication has run. A small round trip (reuse the existing
|
|
115
|
notification mechanism, or a dedicated MSG_SSO_REAUTH response) would let the overlay simply
|
|
116
|
disappear on success and go to the login page on failure, with no page reload, no lost canvas state
|
|
117
|
and no dependence on sessionStorage semantics. That is a larger change and touches SsoTokenManager,
|
|
118
|
so it is recorded here as the direction rather than proposed as the patch.
|
|
119
|
|
|
120
|
2.3 Correct the documentation either way
|
|
121
|
------------------------------------------
|
|
122
|
|
|
123
|
If neither 2.1 nor 2.2 is taken, the comment at p2j.sso_reauth.js:247-254 and the two javadoc blocks
|
|
124
|
at :84-86 and :158-160 must stop claiming a re-attach. Replace with a statement of what actually
|
|
125
|
happens: the reload ends the session and lands on the login page, which is the safe outcome of the
|
|
126
|
two but is not recovery.
|
|
127
|
|
|
128
|
------------------------------------------------------------------------------------------------
|
|
129
|
3. RELATIONSHIP TO OTHER PROPOSALS
|
|
130
|
------------------------------------------------------------------------------------------------
|
|
131
|
|
|
132
|
proposal-issue-minor-functional-3 the leave-confirmation prompt on the same reload; shares the
|
|
133
|
internalReload flag introduced in 2.1. Land together.
|
|
134
|
proposal-issue-minor-functional-4 closing the orphaned popup before the same reload.
|
|
135
|
|
|
136
|
All three touch the same five lines of the expiry branch; apply them as one edit.
|
|
137
|
|
|
138
|
------------------------------------------------------------------------------------------------
|
|
139
|
4. RISK AND VERIFICATION
|
|
140
|
------------------------------------------------------------------------------------------------
|
|
141
|
|
|
142
|
Risk: moderate for 2.1, because it puts a hole in a long-standing invariant. The hole is narrow -
|
|
143
|
one caller, set only immediately before an unload the page itself requested, and never reset (the
|
|
144
|
document is going away) - but a future caller that sets it and then does not unload would leave a
|
|
145
|
GUI page that no longer arms quit-on-reload. Consider naming it to discourage that, and asserting in
|
|
146
|
onpagehide that it is only ever observed once.
|
|
147
|
|
|
148
|
Verification
|
|
149
|
1. Reproduce the target case: trigger SSO re-auth, click Re-login, complete the IdP flow in the
|
|
150
|
popup, and kill the postMessage (close the popup manually the instant it lands on /reauthcb,
|
|
151
|
or serve the callback with a COOP header). Let the countdown expire. Expected with 2.1: the
|
|
152
|
page reloads and comes back into the *same* live session; pre-fix it logs out.
|
|
153
|
2. Let the countdown expire with no re-login attempt at all. Expected: the server force-logout
|
|
154
|
has already fired, the websocket upgrade is rejected, and the page lands on the login page.
|
|
155
|
3. Confirm the invariant is intact for ordinary use: F5 on a GUI page, and closing the tab, must
|
|
156
|
both still quit the session (internalReload is false on those paths).
|
|
157
|
4. Confirm the CHUI client is unaffected - onpagehide returns early on !p2j.isGui, and
|
|
158
|
ChuiWebPageHandler emits isGui=false.
|