Skip to content

Commit 05fdd5c

Browse files
authored
Merge pull request #282 from OneNoteDev/feature/unwriteable-cookie-detect
Feature/unwriteable cookie detect
2 parents 1748339 + 68f1708 commit 05fdd5c

File tree

10 files changed

+157
-164
lines changed

10 files changed

+157
-164
lines changed

src/scripts/clipperUI/panels/signInPanel.tsx

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {ComponentBase} from "../componentBase";
1212
import {Status} from "../status";
1313

1414
interface SignInPanelState {
15-
errorDescriptionShowing?: boolean;
15+
debugInformationShowing?: boolean;
1616
}
1717

1818
interface SignInPanelProps extends ClipperStateProp {
@@ -21,7 +21,7 @@ interface SignInPanelProps extends ClipperStateProp {
2121

2222
class SignInPanelClass extends ComponentBase<SignInPanelState, SignInPanelProps> {
2323
getInitialState() {
24-
return { errorDescriptionShowing: false };
24+
return { debugInformationShowing: false };
2525
}
2626

2727
onSignInMsa() {
@@ -51,61 +51,96 @@ class SignInPanelClass extends ComponentBase<SignInPanelState, SignInPanelProps>
5151
}
5252
}
5353

54-
getSignInFailureDetected(): boolean {
54+
signInAttempted(): boolean {
5555
return !!this.props.clipperState.userResult && !!this.props.clipperState.userResult.data
56-
&& this.props.clipperState.userResult.data.updateReason === UpdateReason.SignInAttempt
56+
&& this.props.clipperState.userResult.data.updateReason === UpdateReason.SignInAttempt;
57+
}
58+
59+
signInFailureContainsErrorDescription(): boolean {
60+
return this.signInAttempted()
61+
&& this.props.clipperState.userResult.data.errorDescription
5762
// Right now we are only showing the error panel for OrgId errors since they tend to
5863
// be a little more actionable to the user, or at least a little more helpful.
59-
&& this.props.clipperState.userResult.data.errorDescription
6064
&& this.props.clipperState.userResult.data.errorDescription.indexOf("OrgId") === 0;
6165
}
6266

63-
errorDescriptionControlHandler() {
64-
this.setState({ errorDescriptionShowing: !this.state.errorDescriptionShowing });
67+
signInFailureThirdPartyCookiesBlocked(): boolean {
68+
return this.signInAttempted()
69+
&& !this.props.clipperState.userResult.data.writeableCookies;
70+
}
71+
72+
debugInformationControlHandler() {
73+
this.setState({ debugInformationShowing: !this.state.debugInformationShowing });
6574
}
6675

67-
errorInformationToggle() {
68-
if (this.getSignInFailureDetected()) {
69-
return <div className="signInErrorToggleInformation">
70-
<a id={Constants.Ids.signInErrorMoreInformation} {...this.enableInvoke(this.errorDescriptionControlHandler, 10) }>
76+
signInErrorDetected() {
77+
return this.signInFailureContainsErrorDescription() || this.signInFailureThirdPartyCookiesBlocked();
78+
}
79+
80+
errorMoreInformationTogggle() {
81+
if (this.signInErrorDetected()) {
82+
return <div id="signInErrorToggleInformation">
83+
<a id={Constants.Ids.signInErrorMoreInformation} {...this.enableInvoke(this.debugInformationControlHandler, 10) }>
7184
<img id={Constants.Ids.signInToggleErrorDropdownArrow} src={ExtensionUtils.getImageResourceUrl("dropdown_arrow.png")} />
7285
<span id={Constants.Ids.signInToggleErrorInformationText}
7386
style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Light)}>
74-
{this.state.errorDescriptionShowing
87+
{this.state.debugInformationShowing
7588
? Localization.getLocalizedString("WebClipper.Label.SignInUnsuccessfulLessInformation")
7689
: Localization.getLocalizedString("WebClipper.Label.SignInUnsuccessfulMoreInformation")
7790
}
7891
</span>
7992
</a>
93+
{this.debugInformation()}
8094
</div>;
8195
}
8296

8397
return undefined;
8498
}
8599

86-
errorInformationDescription() {
87-
if (this.getSignInFailureDetected() && this.state.errorDescriptionShowing) {
88-
return <div id={Constants.Ids.signInErrorDescription}>
89-
<span className={Constants.Ids.signInErrorDescriptionContainer} style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Light)}>
100+
debugInformation() {
101+
if (this.signInErrorDetected() && this.state.debugInformationShowing) {
102+
return <div id={Constants.Ids.signInErrorDebugInformation}>
103+
<span id={Constants.Ids.signInErrorDebugInformationDescription} style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Light)}>
90104
{this.props.clipperState.userResult.data.errorDescription}
91105
</span>
92-
<div className={Constants.Ids.signInErrorDebugInformationContainer}
93-
style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Light)}>
94-
<ul className={Constants.Ids.signInErrorDebugInformationList}>
106+
<div id={Constants.Ids.signInErrorDebugInformationContainer} style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Light)}>
107+
<ul id={Constants.Ids.signInErrorDebugInformationList}>
95108
<li>{ClientType[this.props.clipperState.clientInfo.clipperType]}: {this.props.clipperState.clientInfo.clipperVersion}</li>
96109
<li>ID: {this.props.clipperState.clientInfo.clipperId}</li>
97110
<li>USID: {Clipper.getUserSessionId()}</li>
98111
</ul>
99112
</div>
100113
</div>;
101114
}
115+
}
116+
117+
getErrorDescription() {
118+
if (this.signInFailureContainsErrorDescription()) {
119+
return this.props.clipperState.userResult.data.errorDescription;
120+
} else if (this.signInFailureThirdPartyCookiesBlocked()) {
121+
return <div>
122+
<div class={Constants.Ids.signInErrorCookieInformation}>{Localization.getLocalizedString("WebClipper.Error.CookiesDisabled.Line1")}</div>
123+
<div class={Constants.Ids.signInErrorCookieInformation}>{Localization.getLocalizedString("WebClipper.Error.CookiesDisabled.Line2")}</div>
124+
</div>;
125+
}
102126

103127
return undefined;
104128
}
105129

106-
render() {
107-
let signInDescription = this.getSignInDescription();
130+
errorInformationDescription() {
131+
if (this.signInErrorDetected()) {
132+
return <div id={Constants.Ids.signInErrorDescription}>
133+
<span id={Constants.Ids.signInErrorDescriptionContainer} style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Light)}>
134+
{this.getErrorDescription()}
135+
</span>
136+
{this.errorMoreInformationTogggle()}
137+
</div>;
138+
}
139+
140+
return undefined;
141+
}
108142

143+
render() {
109144
return (
110145
<div id={Constants.Ids.signInContainer}>
111146
<div className="signInPadding">
@@ -119,10 +154,9 @@ class SignInPanelClass extends ComponentBase<SignInPanelState, SignInPanelProps>
119154
<div className="signInDescription">
120155
<span id={Constants.Ids.signInText}
121156
style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Light)}>
122-
{signInDescription}
157+
{this.getSignInDescription()}
123158
</span>
124159
</div>
125-
{this.errorInformationToggle()}
126160
{this.errorInformationDescription()}
127161
<a id={Constants.Ids.signInButtonMsa} {...this.enableInvoke(this.onSignInMsa, 11, AuthType.Msa)}>
128162
<div className="wideButtonContainer">

src/scripts/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ export module Constants {
222222
export var signInButtonMsa = "signInButtonMsa";
223223
export var signInButtonOrgId = "signInButtonOrgId";
224224
export var signInContainer = "signInContainer";
225+
export var signInErrorCookieInformation = "signInErrorCookieInformation";
226+
export var signInErrorDebugInformation = "signInErrorDebugInformation";
227+
export var signInErrorDebugInformationDescription = "signInErrorDebugInformationDescription";
225228
export var signInErrorDebugInformationContainer = "signInErrorDebugInformationContainer";
226229
export var signInErrorDebugInformationList = "signInErrorDebugInformationList";
227230
export var signInErrorDescription = "signInErrorDescription";
@@ -345,7 +348,6 @@ export module Constants {
345348
}
346349

347350
export module Urls {
348-
// export var serviceDomain = "https://minint-per7o4f.redmond.corp.microsoft.com";
349351
export var serviceDomain = "https://www.onenote.com";
350352

351353
export var augmentationApiUrl = serviceDomain + "/onaugmentation/clipperextract/v1.0/";

src/scripts/cookieUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {ObjectUtils} from "./objectUtils";
22

3-
export module CookieUtils {
4-
export function readCookie(cookieName: string, doc?: Document) {
3+
export class CookieUtils {
4+
public static readCookie(cookieName: string, doc?: Document) {
55
if (ObjectUtils.isNullOrUndefined(doc)) {
66
doc = document;
77
}

src/scripts/extensions/authenticationHelper.ts

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,36 +53,35 @@ export class AuthenticationHelper {
5353
}
5454

5555
let getUserInformationFunction = () => {
56-
return new Promise<ResponsePackage<string>>((resolve2, reject2) => {
57-
this.getClipperInfoCookie(clipperId).then((cookie) => {
58-
this.retrieveUserInformation(clipperId, cookie).then((result) => {
59-
resolve2(result);
60-
}, (errorObject) => {
61-
reject2(errorObject);
62-
});
63-
});
56+
return this.getClipperInfoCookie(clipperId).then((cookie) => {
57+
return this.retrieveUserInformation(clipperId, cookie);
6458
});
6559
};
6660

6761
let getInfoEvent: Log.Event.PromiseEvent = new Log.Event.PromiseEvent(Log.Event.Label.GetExistingUserInformation);
6862
getInfoEvent.setCustomProperty(Log.PropertyName.Custom.UserInformationStored, !!storedUserInformation);
6963
this.clipperData.getFreshValue(ClipperStorageKeys.userInformation, getUserInformationFunction, updateInterval).then((response: TimeStampedData) => {
70-
getInfoEvent.setCustomProperty(Log.PropertyName.Custom.FreshUserInfoAvailable, !!response);
64+
let isValidUser = this.isValidUserInformation(response.data);
65+
getInfoEvent.setCustomProperty(Log.PropertyName.Custom.FreshUserInfoAvailable, isValidUser);
7166

72-
if (response) {
73-
this.user.set({ user: response.data, lastUpdated: response.lastUpdated, updateReason: updateReason });
67+
let writeableCookies = this.isThirdPartyCookiesEnabled(response.data);
68+
getInfoEvent.setCustomProperty(Log.PropertyName.Custom.WriteableCookies, writeableCookies);
69+
70+
if (isValidUser) {
71+
this.user.set({ user: response.data, lastUpdated: response.lastUpdated, updateReason: updateReason, writeableCookies: writeableCookies });
7472
} else {
75-
this.user.set({ updateReason: updateReason });
73+
this.user.set({ updateReason: updateReason, writeableCookies: writeableCookies });
7674
}
7775

78-
resolve(this.user.get());
7976
}, (error: OneNoteApi.GenericError) => {
8077
getInfoEvent.setStatus(Log.Status.Failed);
8178
getInfoEvent.setFailureInfo(error);
79+
8280
this.user.set({ updateReason: updateReason });
83-
resolve(this.user.get());
8481
}).then(() => {
8582
this.logger.logEvent(getInfoEvent);
83+
84+
resolve(this.user.get());
8685
});
8786
});
8887
}
@@ -157,8 +156,8 @@ export class AuthenticationHelper {
157156

158157
HttpWithRetries.post(userInfoUrl, postData, headers).then((request: XMLHttpRequest) => {
159158
let response = request.response;
160-
// The false case is expected behavior if the user has not signed in or credentials have expired
161-
resolve({ parsedResponse: this.isValidUserInformationJsonString(response) ? response : undefined, request: request });
159+
160+
resolve({ parsedResponse: response, request: request });
162161
}, (error: OneNoteApi.RequestError) => {
163162
retrieveUserInformationEvent.setStatus(Log.Status.Failed);
164163
retrieveUserInformationEvent.setFailureInfo(error);
@@ -172,19 +171,19 @@ export class AuthenticationHelper {
172171
/**
173172
* Determines whether or not the given string is valid JSON and has the required elements.
174173
*/
175-
public isValidUserInformationJsonString(userInfo: string): boolean {
176-
let userInfoJson: UserInfoData;
177-
try {
178-
userInfoJson = JSON.parse(userInfo);
179-
} catch (e) {
180-
// intentionally not logging this as a JsonParse failure
181-
return false;
182-
}
183-
184-
if (userInfoJson && userInfoJson.accessToken && userInfoJson.accessTokenExpiration > 0 && userInfoJson.authType) {
174+
protected isValidUserInformation(userInfo: UserInfoData): boolean {
175+
if (userInfo && userInfo.accessToken && userInfo.accessTokenExpiration > 0 && userInfo.authType) {
185176
return true;
186177
}
187178

188179
return false;
189180
}
181+
182+
/**
183+
* Determines whether or not the given string is valid JSON and has the flag which lets us know if cookies are enabled.
184+
*/
185+
protected isThirdPartyCookiesEnabled(userInfo: UserInfoData): boolean {
186+
// Note that we are returning true by default to ensure the N-1 scenario.
187+
return userInfo.cookieInRequest !== undefined ? userInfo.cookieInRequest : true;
188+
}
190189
}

src/scripts/logging/submodules/propertyName.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export module PropertyName {
5959
Value,
6060
VideoDataOriginalSrcUrl,
6161
VideoSrcUrl,
62-
Width
62+
Width,
63+
WriteableCookies
6364
}
6465

6566
/* tslint:disable:variable-name */

src/scripts/userInfo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ export interface UserInfo {
88
lastUpdated?: number;
99
updateReason: UpdateReason;
1010
errorDescription?: string;
11+
writeableCookies?: boolean;
1112
}
1213

1314
export interface UserInfoData {
1415
accessToken?: string;
1516
accessTokenExpiration?: number;
1617
authType?: string;
1718
cid?: string;
19+
cookieInRequest?: boolean;
1820
emailAddress?: string;
1921
fullName?: string;
2022
}

src/strings.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,25 @@
4242
"WebClipper.ClipType.Selection.Button": "Selection",
4343
"WebClipper.ClipType.Selection.Button.Tooltip": "Clip the selection you made on the web page.",
4444
"WebClipper.ClipType.Selection.ProgressLabel": "Clipping Selection",
45-
"WebClipper.Error.NoOpError": "Sorry, we can\u0027t clip this page right now",
4645
"WebClipper.Error.ConflictingExtension": "Your PDF viewer or another extension might be blocking the OneNote Web Clipper. You could temporarily disable the following extension and try clipping again.",
4746
"WebClipper.Error.CannotClipPage": "Sorry, this type of page can\u0027t be clipped.",
47+
"WebClipper.Error.CookiesDisabled.Line1": "Cookies must be enabled in order for OneNote Web Clipper to work correctly.",
48+
"WebClipper.Error.CookiesDisabled.Line2": "Please allow third-party cookies in your browser or add the onenote.com domain as an exception.",
4849
"WebClipper.Error.CorruptedSection": "Your clip can\u0027t be saved here because the section is corrupt.",
4950
"WebClipper.Error.GenericError": "Something went wrong. Please try clipping the page again.",
51+
"WebClipper.Error.GenericExpiredTokenRefreshError": "Your login session has ended and we were unable to clip the page. Please sign in again.",
52+
"WebClipper.Error.NoOpError": "Sorry, we can\u0027t clip this page right now",
5053
"WebClipper.Error.NotProvisioned": "Your clip can\u0027t be saved because your OneDrive for Business account isn\u0027t set up.",
54+
"WebClipper.Error.OrphanedWebClipperDetected": "Something went wrong. Please refresh this page, and try to clip again.",
5155
"WebClipper.Error.PasswordProtected": "Your clip can\u0027t be saved here because the section is password protected.",
5256
"WebClipper.Error.QuotaExceeded": "Your clip can\u0027t be saved because your OneDrive account has reached its size limit.",
5357
"WebClipper.Error.ResourceDoesNotExist": "Your clip can\u0027t be saved here because the location no longer exists. Please try clipping to another location.",
5458
"WebClipper.Error.SectionTooLarge": "Your clip can\u0027t be saved here because the section has reached its size limit.",
59+
"WebClipper.Error.SignInUnsuccessful": "We couldn\u0027t sign you in. Please try again.",
60+
"WebClipper.Error.ThirdPartyCookiesDisabled": "For OneNote Web Clipper to work correctly, please allow third-party cookies in your browser, or add the onenote.com domain as an exception.",
5561
"WebClipper.Error.UserAccountSuspended": "Your clip can\u0027t be saved because your Microsoft account has been suspended.",
5662
"WebClipper.Error.UserAccountSuspendedResetText": "Reset Your Account",
5763
"WebClipper.Error.UserDoesNotHaveUpdatePermission": "We\u0027ve added features to the Web Clipper that require new permissions. To accept them, please sign out and sign back in.",
58-
"WebClipper.Error.GenericExpiredTokenRefreshError": "Your login session has ended and we were unable to clip the page. Please sign in again.",
59-
"WebClipper.Error.SignInUnsuccessful": "We couldn\u0027t sign you in. Please try again.",
60-
"WebClipper.Error.OrphanedWebClipperDetected": "Something went wrong. Please refresh this page, and try to clip again.",
6164
"WebClipper.Extension.RefreshTab": "Please refresh this page, and try to clip again.",
6265
"WebClipper.FromCitation": "Clipped from: {0}",
6366
"WebClipper.Label.Annotation": "Note",
@@ -128,4 +131,4 @@
128131
"WebClipper.FontFamily.Semilight": "Segoe UI Semilight,Segoe UI Light,Segoe WP Light,Segoe UI,Segoe,Segoe WP,Helvetica Neue,Roboto,Helvetica,Arial,Tahoma,Verdana,sans-serif",
129132
"WebClipper.FontSize.Preview.SerifDefault": "16px",
130133
"WebClipper.FontSize.Preview.SansSerifDefault": "16px"
131-
}
134+
}

src/styles/signInPanelStyles.less

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
padding-bottom: 28px;
99
}
1010

11+
.signInErrorCookieInformation {
12+
padding-bottom: 10px;
13+
}
14+
1115
#signInErrorDescription {
1216
color: white;
1317
font-size: 13px;
14-
padding-bottom: 20px;
1518
text-align: left;
1619
-moz-user-select: text;
1720
-khtml-user-select: text;
@@ -20,21 +23,22 @@
2023
user-select: text;
2124
}
2225

23-
.signInErrorDebugInformationContainer {
26+
#signInErrorDebugInformationContainer {
2427
color: white;
2528
font-size: 13px;
2629
text-align: left;
2730
}
2831

29-
.signInErrorDebugInformationList {
32+
#signInErrorDebugInformationList {
3033
list-style: none;
3134
padding-left: 0;
32-
font-size: 13px;
35+
font-size: 12px;
3336
}
3437

35-
.signInErrorToggleInformation {
38+
#signInErrorToggleInformation {
3639
text-align: left;
37-
padding-bottom: 10px;
40+
padding-bottom: 15px;
41+
padding-top: 15px;
3842
font-size: 13px;
3943
}
4044

0 commit comments

Comments
 (0)