Skip to content

Commit 12f5331

Browse files
committed
Merge latest upstream/main into KJ21-ENG/98233-august-product-marketing-window
2 parents 3e5dd44 + 9b02826 commit 12f5331

7 files changed

Lines changed: 76 additions & 15 deletions

File tree

src/hooks/useParticipantSubmission.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ function useParticipantSubmission({
113113
const isActivePolicyRequest =
114114
iouType === CONST.IOU.TYPE.CREATE &&
115115
isGroupPolicy(activePolicy) &&
116-
activePolicy?.isPolicyExpenseChatEnabled &&
117116
!shouldRestrictUserBillableActions(activePolicy, ownerBillingGracePeriodEnd, userBillingGracePeriodEnds, amountOwed, currentUserPersonalDetails.accountID);
118117

119118
const dataRef = useRef({

src/hooks/useReceiptScanDrop.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ function useReceiptScanDrop() {
8686

8787
if (
8888
isGroupPolicy(activePolicy) &&
89-
activePolicy?.isPolicyExpenseChatEnabled &&
9089
!shouldRestrictUserBillableActions(activePolicy, ownerBillingGracePeriodEnd, userBillingGracePeriodEnds, amountOwed, currentUserPersonalDetails.accountID)
9190
) {
9291
const shouldAutoReport = !!activePolicy?.autoReporting || !!personalPolicy?.autoReporting;

src/libs/ExportOnyxState/common.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@ const ONYX_KEY_EXPORT_RULES: Record<string, ExportRule> = {
5757
allowList: ['email', 'accountID', 'loading', 'creationDate', 'errors'],
5858
maskList: [],
5959
},
60+
// delegateAccessData holds live OldDot credentials (auth tokens, an auto-generated login and its
61+
// password). A rule masks them in both toggle states; maskFragileData only runs when masking is on and
62+
// would let a credential field pass through unless its exact name is in keysToMask.
63+
[ONYXKEYS.HYBRID_APP]: {
64+
allowList: [
65+
'useNewDotSignInPage',
66+
'readyToShowAuthScreens',
67+
'isSingleNewDotEntry',
68+
'loggedOutFromOldDot',
69+
'shouldRemoveDelegatedAccess',
70+
'closingReactNativeApp',
71+
'shouldUseNewPartnerName',
72+
'signingInWithSAML',
73+
'pressedTryNewExpensify',
74+
'transitionStartTimestamp',
75+
'isDelegateAccess',
76+
'oldDotCurrentAccountID',
77+
],
78+
maskList: ['oldDotCurrentUserEmail', 'oldDotCurrentAuthToken', 'oldDotCurrentEncryptedAuthToken', 'oldDotAutoGeneratedLogin', 'oldDotAutoGeneratedPassword'],
79+
},
6080
[ONYXKEYS.CREDENTIALS]: {
6181
allowList: ['login', 'accountID'],
6282
maskList: [],
@@ -388,7 +408,6 @@ const onyxKeysToMaskFragileData = new Set<string>([
388408
ONYXKEYS.FREQUENTLY_USED_EMOJIS,
389409
ONYXKEYS.FUND_LIST,
390410
ONYXKEYS.GPS_DRAFT_DETAILS,
391-
ONYXKEYS.HYBRID_APP,
392411
ONYXKEYS.IMPORTED_SPREADSHEET,
393412
ONYXKEYS.IMPORTED_SPREADSHEET_MEMBER_DATA,
394413
ONYXKEYS.INITIATING_BANK_ACCOUNT_UNLOCK,
@@ -490,6 +509,7 @@ const keysToMask = new Set([
490509
'addressName',
491510
'addressStreet',
492511
'addressZipCode',
512+
'authToken',
493513
'avatar',
494514
'avatarURL',
495515
'bank',
@@ -501,6 +521,7 @@ const keysToMask = new Set([
501521
'description',
502522
'displayName',
503523
'edits',
524+
'encryptedAuthToken',
504525
'firstName',
505526
'lastMessageHtml',
506527
'lastMessageText',
@@ -524,6 +545,7 @@ const keysToMask = new Set([
524545
'source',
525546
'state',
526547
'street',
548+
'supportAuthToken',
527549
'title',
528550
'validateCode',
529551
'zip',
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {IOUType} from '@src/CONST';
22
import CONST from '@src/CONST';
3-
import type {BillingGraceEndPeriod, OnyxInputOrEntry, Policy} from '@src/types/onyx';
3+
import type {BillingGraceEndPeriod, Policy} from '@src/types/onyx';
44

55
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
66

@@ -9,18 +9,17 @@ import {shouldRestrictUserBillableActions} from './SubscriptionUtils';
99

1010
function shouldUseDefaultExpensePolicy(
1111
iouType: IOUType,
12-
defaultExpensePolicy: OnyxInputOrEntry<Policy>,
12+
defaultExpensePolicy: OnyxEntry<Policy> | null,
1313
amountOwed: OnyxEntry<number>,
1414
userBillingGracePeriodEnds: OnyxCollection<BillingGraceEndPeriod>,
1515
ownerBillingGracePeriodEnd: OnyxEntry<number>,
1616
currentUserAccountID: number,
1717
) {
18-
return (
19-
iouType === CONST.IOU.TYPE.CREATE &&
20-
isGroupPolicy(defaultExpensePolicy) &&
21-
defaultExpensePolicy?.isPolicyExpenseChatEnabled &&
22-
!shouldRestrictUserBillableActions(defaultExpensePolicy, ownerBillingGracePeriodEnd, userBillingGracePeriodEnds, amountOwed, currentUserAccountID)
23-
);
18+
if (iouType !== CONST.IOU.TYPE.CREATE || !defaultExpensePolicy || !isGroupPolicy(defaultExpensePolicy)) {
19+
return false;
20+
}
21+
22+
return !shouldRestrictUserBillableActions(defaultExpensePolicy, ownerBillingGracePeriodEnd, userBillingGracePeriodEnds, amountOwed, currentUserAccountID);
2423
}
2524

2625
export default shouldUseDefaultExpensePolicy;

tests/actions/IOU/MoneyRequestTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,10 +1790,10 @@ describe('MoneyRequest', () => {
17901790
expect(shouldUseDefaultExpensePolicy(CONST.IOU.TYPE.CREATE, policy, 0, undefined, undefined, currentUserAccountID)).toBe(false);
17911791
});
17921792

1793-
it('should return false when isPolicyExpenseChatEnabled is false', () => {
1793+
it('should return false when policy is not a group policy', () => {
17941794
const policy = {
17951795
...fakePolicy,
1796-
type: CONST.POLICY.TYPE.TEAM,
1796+
type: CONST.POLICY.TYPE.PERSONAL,
17971797
isPolicyExpenseChatEnabled: false,
17981798
};
17991799

tests/unit/ExportOnyxStateTest.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,36 @@ describe('maskOnyxState', () => {
399399
expect(result.lastMessageHtml).not.toBe(input.lastMessageHtml);
400400
}
401401
});
402+
403+
it.each([
404+
['masking enabled', true],
405+
['masking disabled', false],
406+
])('should mask delegate credentials held under hybridApp with %s', (_label, isMaskingEnabled) => {
407+
// Given a hybridApp key holding live OldDot delegate credentials in delegateAccessData
408+
const credentialValues = ['live-olddot-auth-token', 'live-olddot-encrypted-token', 'auto-generated-login', 'auto-generated-password', 'delegate@example.com'];
409+
const input = {
410+
session: mockSession,
411+
[ONYXKEYS.HYBRID_APP]: {
412+
isSingleNewDotEntry: true,
413+
delegateAccessData: {
414+
isDelegateAccess: true,
415+
oldDotCurrentAuthToken: 'live-olddot-auth-token',
416+
oldDotCurrentEncryptedAuthToken: 'live-olddot-encrypted-token',
417+
oldDotAutoGeneratedLogin: 'auto-generated-login',
418+
oldDotAutoGeneratedPassword: 'auto-generated-password',
419+
oldDotCurrentUserEmail: 'delegate@example.com',
420+
},
421+
},
422+
};
423+
424+
// When the state is exported (the export rule applies regardless of the masking toggle)
425+
const serialized = JSON.stringify(maskOnyxState(input, isMaskingEnabled));
426+
427+
// Then none of the credential values may appear verbatim anywhere in the export
428+
for (const credentialValue of credentialValues) {
429+
expect(serialized).not.toContain(credentialValue);
430+
}
431+
});
402432
});
403433

404434
// These tests check that every Onyx key is sorted into a bucket and that no key lands in two of them.
@@ -457,6 +487,7 @@ describe('Onyx key export coverage', () => {
457487
ONYXKEYS.ACCOUNT,
458488
ONYXKEYS.PRESERVED_USER_SESSION,
459489
ONYXKEYS.PRESERVED_ACCOUNT,
490+
ONYXKEYS.HYBRID_APP,
460491
ONYXKEYS.PERSONAL_DETAILS_LIST,
461492
ONYXKEYS.PRIVATE_PERSONAL_DETAILS,
462493
ONYXKEYS.LOGINS,
@@ -488,6 +519,17 @@ describe('Onyx key export coverage', () => {
488519
}
489520
});
490521

522+
it('session token field names must be in keysToMask so the maskFragileData fallback can never pass them through', () => {
523+
// maskFragileData exports any field name it does not recognize verbatim. Keys that reach it and hold a
524+
// session-token field rely on keysToMask to catch the secret, so these generic field names must stay
525+
// listed as a backstop even though the keys that carry them today have their own export rules.
526+
const credentialFieldNames = ['authToken', 'encryptedAuthToken', 'supportAuthToken'];
527+
528+
for (const fieldName of credentialFieldNames) {
529+
expect(keysToMask.has(fieldName)).toBe(true);
530+
}
531+
});
532+
491533
it('no key should appear in multiple buckets', () => {
492534
const rulesKeys = Object.keys(ONYX_KEY_EXPORT_RULES);
493535
const removeKeys = new Set<string>(Array.from(onyxKeysToRemove).filter((key): key is Extract<typeof key, string> => typeof key === 'string'));

tests/unit/shouldUseDefaultExpensePolicyTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ describe('shouldUseDefaultExpensePolicy', () => {
4949
expect(shouldUseDefaultExpensePolicy(CONST.IOU.TYPE.CREATE, policy, undefined, undefined, undefined, CONST.DEFAULT_NUMBER_ID)).toBeFalsy();
5050
});
5151

52-
it('returns false when isPolicyExpenseChatEnabled is false', () => {
53-
const policy = makePaidGroupPolicy({isPolicyExpenseChatEnabled: false});
52+
it('returns false when defaultExpensePolicy is not a group policy', () => {
53+
const policy = makePaidGroupPolicy({type: CONST.POLICY.TYPE.PERSONAL, isPolicyExpenseChatEnabled: false});
5454
expect(shouldUseDefaultExpensePolicy(CONST.IOU.TYPE.CREATE, policy, undefined, undefined, undefined, CONST.DEFAULT_NUMBER_ID)).toBeFalsy();
5555
});
5656

0 commit comments

Comments
 (0)