fix: move wrapped storage to unwrapped if not encrypted (#1603)

This commit is contained in:
Mo
2022-09-20 12:18:00 -05:00
committed by GitHub
parent cb4c0f1b5c
commit c5e225d335
21 changed files with 134 additions and 56 deletions

View File

@@ -1,2 +1,19 @@
node_modules
scripts
scripts
actions
.yarn
.vscode
.sass-cache
.husky
.github
.git
**/dist
**/coverage
**/node_modules
packages/components
packages/docs
packages/mobile/android
packages/mobile/ios
packages/mobile/html
packages/snjs/mocha
*.d.ts

View File

@@ -1,4 +1,4 @@
{
"extends": ["./node_modules/@standardnotes/config/src/.eslintrc"],
"ignorePatterns": [".eslintrc.js", "*.webpack.*.js", "webpack-defaults.js", "jest.config.js", "__mocks__"]
"ignorePatterns": [".eslintrc.js", "*.webpack.*.js", "webpack-defaults.js", "jest.config.js", "__mocks__", "**/**/coverage"]
}

View File

@@ -1,2 +1,3 @@
node_modules
dist
coverage

View File

@@ -0,0 +1,3 @@
node_modules
dist
jsign

View File

@@ -5,4 +5,5 @@ ios
e2e
android
fastlane
WebFrame
WebFrame
__tests__

View File

@@ -570,7 +570,7 @@ export class ApplicationState extends ApplicationService {
).includes(state)
}
private async getScreenshotPrivacyEnabled(): Promise<boolean | undefined> {
private async getScreenshotPrivacyEnabled(): Promise<boolean> {
return this.application.getMobileScreenshotPrivacyEnabled()
}

View File

@@ -73,6 +73,11 @@ export class MobileDevice implements MobileDeviceInterface {
;(this.stateObserverService as unknown) = undefined
}
consoleLog(...args: any[]): void {
// eslint-disable-next-line no-console
console.log(args)
}
async setLegacyRawKeychainValue(value: LegacyRawKeychainValue): Promise<void> {
await Keychain.setKeys(value)
}
@@ -378,7 +383,7 @@ export class MobileDevice implements MobileDeviceInterface {
await Keychain.clearKeys()
}
async setAndroidScreenshotPrivacy(enable: boolean): Promise<void> {
setAndroidScreenshotPrivacy(enable: boolean): void {
if (Platform.OS === 'android') {
enable ? FlagSecure.activate() : FlagSecure.deactivate()
}

View File

@@ -38,14 +38,14 @@ export const SecuritySection = (props: Props) => {
useEffect(() => {
let mounted = true
const getHasScreenshotPrivacy = async () => {
const hasScreenshotPrivacyEnabled = (await application?.getMobileScreenshotPrivacyEnabled()) ?? true
const hasScreenshotPrivacyEnabled = application?.getMobileScreenshotPrivacyEnabled()
if (mounted) {
setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled)
}
}
void getHasScreenshotPrivacy()
const getHasBiometrics = async () => {
const appHasBiometrics = await application!.hasBiometrics()
const appHasBiometrics = application!.hasBiometrics()
if (mounted) {
setHasBiometrics(appHasBiometrics)
}

View File

@@ -1,2 +1,3 @@
node_modules
dist
coverage

View File

@@ -6,8 +6,9 @@ export interface MobileDeviceInterface extends DeviceInterface {
getRawKeychainValue(): Promise<RawKeychainValue | undefined>
getDeviceBiometricsAvailability(): Promise<boolean>
setAndroidScreenshotPrivacy(enable: boolean): Promise<void>
setAndroidScreenshotPrivacy(enable: boolean): void
authenticateWithBiometrics(): Promise<boolean>
hideMobileInterfaceFromScreenshots(): void
stopHidingMobileInterfaceFromScreenshots(): void
consoleLog(...args: any[]): void
}

View File

@@ -2,4 +2,8 @@ node_modules
dist
test
*.config.js
mocha/**/*
mocha/**/*
coverage
e2e-server.js
jest-global.ts
webpack.*.js

View File

@@ -324,6 +324,7 @@ export class SNApplication
)
}
}
await this.handleStage(ExternalServices.ApplicationStage.StorageDecrypted_09)
this.apiService.loadHost()
@@ -929,30 +930,30 @@ export class SNApplication
return this.deinit(this.getDeinitMode(), DeinitSource.Lock)
}
async setBiometricsTiming(timing: MobileUnlockTiming) {
setBiometricsTiming(timing: MobileUnlockTiming) {
return this.protectionService.setBiometricsTiming(timing)
}
async getMobileScreenshotPrivacyEnabled(): Promise<boolean | undefined> {
getMobileScreenshotPrivacyEnabled(): boolean {
return this.protectionService.getMobileScreenshotPrivacyEnabled()
}
async getMobilePasscodeTiming(): Promise<MobileUnlockTiming | undefined> {
return this.getValue(StorageKey.MobilePasscodeTiming, StorageValueModes.Nonwrapped) as Promise<
MobileUnlockTiming | undefined
>
}
async getMobileBiometricsTiming(): Promise<MobileUnlockTiming | undefined> {
return this.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped) as Promise<
MobileUnlockTiming | undefined
>
}
async setMobileScreenshotPrivacyEnabled(isEnabled: boolean) {
setMobileScreenshotPrivacyEnabled(isEnabled: boolean) {
return this.protectionService.setMobileScreenshotPrivacyEnabled(isEnabled)
}
getMobilePasscodeTiming(): MobileUnlockTiming | undefined {
return this.getValue(StorageKey.MobilePasscodeTiming, StorageValueModes.Nonwrapped) as
| MobileUnlockTiming
| undefined
}
getMobileBiometricsTiming(): MobileUnlockTiming | undefined {
return this.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped) as
| MobileUnlockTiming
| undefined
}
async loadMobileUnlockTiming() {
return this.protectionService.loadMobileUnlockTiming()
}

View File

@@ -257,36 +257,36 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
]
}
private async getBiometricsTiming(): Promise<MobileUnlockTiming | undefined> {
return this.storageService.getValue<Promise<MobileUnlockTiming | undefined>>(
private getBiometricsTiming(): MobileUnlockTiming | undefined {
return this.storageService.getValue<MobileUnlockTiming | undefined>(
StorageKey.MobileBiometricsTiming,
StorageValueModes.Nonwrapped,
)
}
private async getPasscodeTiming(): Promise<MobileUnlockTiming | undefined> {
return this.storageService.getValue<Promise<MobileUnlockTiming | undefined>>(
private getPasscodeTiming(): MobileUnlockTiming | undefined {
return this.storageService.getValue<MobileUnlockTiming | undefined>(
StorageKey.MobilePasscodeTiming,
StorageValueModes.Nonwrapped,
)
}
async setBiometricsTiming(timing: MobileUnlockTiming) {
await this.storageService.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped)
this.storageService.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped)
this.mobileBiometricsTiming = timing
}
async setMobileScreenshotPrivacyEnabled(isEnabled: boolean) {
setMobileScreenshotPrivacyEnabled(isEnabled: boolean) {
return this.storageService.setValue(StorageKey.MobileScreenshotPrivacyEnabled, isEnabled, StorageValueModes.Default)
}
async getMobileScreenshotPrivacyEnabled(): Promise<boolean | undefined> {
return this.storageService.getValue(StorageKey.MobileScreenshotPrivacyEnabled, StorageValueModes.Default)
getMobileScreenshotPrivacyEnabled(): boolean {
return this.storageService.getValue(StorageKey.MobileScreenshotPrivacyEnabled, StorageValueModes.Default, false)
}
async loadMobileUnlockTiming() {
this.mobilePasscodeTiming = await this.getPasscodeTiming()
this.mobileBiometricsTiming = await this.getBiometricsTiming()
loadMobileUnlockTiming(): void {
this.mobilePasscodeTiming = this.getPasscodeTiming()
this.mobileBiometricsTiming = this.getBiometricsTiming()
}
private async validateOrRenewSession(

View File

@@ -0,0 +1,33 @@
import { DiskStorageService } from './DiskStorageService'
import { InternalEventBus, DeviceInterface, InternalEventBusInterface } from '@standardnotes/services'
import { Environment } from '@standardnotes/models'
describe('diskStorageService', () => {
let storageService: DiskStorageService
let internalEventBus: InternalEventBusInterface
let device: DeviceInterface
beforeEach(() => {
internalEventBus = {} as jest.Mocked<InternalEventBus>
device = {} as jest.Mocked<DeviceInterface>
storageService = new DiskStorageService(device, 'test', Environment.Desktop, internalEventBus)
})
it('setInitialValues should set unwrapped values as wrapped value if wrapped value is not encrypted', async () => {
storageService.isStorageWrapped = jest.fn().mockReturnValue(false)
await storageService['setInitialValues']({
wrapped: { content: { foo: 'bar' } } as never,
nonwrapped: {},
unwrapped: { bar: 'zoo' },
})
expect(storageService['values']).toEqual({
wrapped: { content: { foo: 'bar' } } as never,
nonwrapped: {},
unwrapped: { bar: 'zoo', foo: 'bar' },
})
})
})

View File

@@ -112,14 +112,10 @@ export class DiskStorageService extends Services.AbstractService implements Serv
const value = await this.deviceInterface.getRawStorageValue(this.getPersistenceKey())
const values = value ? JSON.parse(value as string) : undefined
this.setInitialValues(values)
await this.setInitialValues(values)
}
/**
* Called by platforms with the value they load from disk,
* after they handle initializeFromDisk
*/
private setInitialValues(values?: Services.StorageValuesObject) {
private async setInitialValues(values?: Services.StorageValuesObject) {
const sureValues = values || this.defaultValuesObject()
if (!sureValues[Services.ValueModesKeys.Unwrapped]) {
@@ -127,6 +123,13 @@ export class DiskStorageService extends Services.AbstractService implements Serv
}
this.values = sureValues
if (!this.isStorageWrapped()) {
this.values[Services.ValueModesKeys.Unwrapped] = {
...(this.values[Services.ValueModesKeys.Wrapped].content as object),
...this.values[Services.ValueModesKeys.Unwrapped],
}
}
}
public isStorageWrapped(): boolean {
@@ -370,7 +373,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
* Clears simple values from storage only. Does not affect payloads.
*/
async clearValues() {
this.setInitialValues()
await this.setInitialValues()
await this.immediatelyPersistValuesToDisk()
}

View File

@@ -1,5 +1,5 @@
module.exports = {
extends: ['../.eslintrc.js'],
extends: ['../.eslintrc'],
globals: {
chai: true,
chaiAsPromised: true,

View File

@@ -0,0 +1,4 @@
dist
node_modules
web.webpack-defaults.js
coverage

View File

@@ -19,7 +19,9 @@
"__mocks__",
"src/components",
"src/favicon",
"src/vendor"
"src/vendor",
"coverage",
"*.config.js"
],
"rules": {
"standard/no-callback-literal": 0, // Disable this as we have too many callbacks relying on literals

View File

@@ -76,6 +76,11 @@ export class WebApplication extends SNApplication implements WebApplicationInter
if (this.isNativeMobileWeb()) {
this.mobileWebReceiver = new MobileWebReceiver(this)
// eslint-disable-next-line no-console
console.log = (...args) => {
this.mobileDevice.consoleLog(...args)
}
}
this.onVisibilityChange = () => {
@@ -228,7 +233,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
async handleMobileGainingFocusEvent(): Promise<void> {}
async handleMobileLosingFocusEvent(): Promise<void> {
if (await this.getMobileScreenshotPrivacyEnabled()) {
if (this.getMobileScreenshotPrivacyEnabled()) {
this.mobileDevice.stopHidingMobileInterfaceFromScreenshots()
}
@@ -236,7 +241,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
}
async handleMobileResumingFromBackgroundEvent(): Promise<void> {
if (await this.getMobileScreenshotPrivacyEnabled()) {
if (this.getMobileScreenshotPrivacyEnabled()) {
this.mobileDevice.hideMobileInterfaceFromScreenshots()
}
}

View File

@@ -20,7 +20,7 @@ const BiometricsLock = ({ application }: Props) => {
useEffect(() => {
const getHasBiometrics = async () => {
const appHasBiometrics = await application.hasBiometrics()
const appHasBiometrics = application.hasBiometrics()
setHasBiometrics(appHasBiometrics)
}
@@ -50,7 +50,7 @@ const BiometricsLock = ({ application }: Props) => {
await disableBiometrics()
} else {
setHasBiometrics(true)
await application.enableBiometrics()
application.enableBiometrics()
await setBiometricsTimingValue(MobileUnlockTiming.OnQuit)
}
}

View File

@@ -13,22 +13,19 @@ type Props = {
}
const MultitaskingPrivacy = ({ application }: Props) => {
const [hasScreenshotPrivacy, setHasScreenshotPrivacy] = useState<boolean | undefined>(false)
const [hasScreenshotPrivacy, setHasScreenshotPrivacy] = useState<boolean>(false)
useEffect(() => {
const getHasScreenshotPrivacy = async () => {
const hasScreenshotPrivacyEnabled = (await application.getMobileScreenshotPrivacyEnabled()) ?? true
setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled)
}
void getHasScreenshotPrivacy()
const hasScreenshotPrivacyEnabled = application.getMobileScreenshotPrivacyEnabled()
setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled)
}, [application])
const onScreenshotPrivacyPress = async () => {
const enable = !hasScreenshotPrivacy
setHasScreenshotPrivacy(enable)
await application.setMobileScreenshotPrivacyEnabled(enable)
await (application.deviceInterface as MobileDeviceInterface).setAndroidScreenshotPrivacy(enable)
application.setMobileScreenshotPrivacyEnabled(enable)
;(application.deviceInterface as MobileDeviceInterface).setAndroidScreenshotPrivacy(enable)
}
const screenshotPrivacyFeatureText = isIOS() ? 'Multitasking Privacy' : 'Multitasking/Screenshot Privacy'