2018-03-09 22:59:12 +02:00
const BaseItem = require ( 'lib/models/BaseItem' ) ;
2019-05-28 23:05:11 +02:00
const MasterKey = require ( 'lib/models/MasterKey' ) ;
2018-10-08 20:11:53 +02:00
const Resource = require ( 'lib/models/Resource' ) ;
2019-05-12 16:53:42 +02:00
const ResourceService = require ( 'lib/services/ResourceService' ) ;
2019-06-08 00:11:08 +02:00
const KvStore = require ( 'lib/services/KvStore' ) ;
2018-03-09 22:59:12 +02:00
const { Logger } = require ( 'lib/logger.js' ) ;
2019-05-22 16:56:07 +02:00
const EventEmitter = require ( 'events' ) ;
2017-12-14 20:53:08 +02:00
2017-12-14 19:58:10 +02:00
class DecryptionWorker {
2018-03-09 22:59:12 +02:00
2017-12-14 19:58:10 +02:00
constructor ( ) {
2018-03-09 22:59:12 +02:00
this . state _ = 'idle' ;
2017-12-31 16:23:05 +02:00
this . logger _ = new Logger ( ) ;
2017-12-14 20:53:08 +02:00
2018-03-09 22:59:12 +02:00
this . dispatch = ( action ) => {
2017-12-17 21:51:45 +02:00
//console.warn('DecryptionWorker.dispatch is not defined');
2017-12-14 20:53:08 +02:00
} ;
2017-12-14 21:39:13 +02:00
this . scheduleId _ = null ;
2019-05-22 16:56:07 +02:00
this . eventEmitter _ = new EventEmitter ( ) ;
2019-06-08 00:11:08 +02:00
this . kvStore _ = null ;
this . maxDecryptionAttempts _ = 2 ;
2017-12-14 21:39:13 +02:00
}
setLogger ( l ) {
this . logger _ = l ;
}
logger ( ) {
return this . logger _ ;
2017-12-14 20:53:08 +02:00
}
2019-05-22 16:56:07 +02:00
on ( eventName , callback ) {
return this . eventEmitter _ . on ( eventName , callback ) ;
}
off ( eventName , callback ) {
return this . eventEmitter _ . removeListener ( eventName , callback ) ;
}
2017-12-14 20:53:08 +02:00
static instance ( ) {
if ( this . instance _ ) return this . instance _ ;
this . instance _ = new DecryptionWorker ( ) ;
return this . instance _ ;
}
2017-12-14 21:39:13 +02:00
setEncryptionService ( v ) {
this . encryptionService _ = v ;
}
2019-06-08 00:11:08 +02:00
setKvStore ( v ) {
this . kvStore _ = v ;
}
2017-12-14 21:39:13 +02:00
encryptionService ( ) {
2018-03-09 22:59:12 +02:00
if ( ! this . encryptionService _ ) throw new Error ( 'DecryptionWorker.encryptionService_ is not set!!' ) ;
2017-12-14 20:53:08 +02:00
return this . encryptionService _ ;
2017-12-14 19:58:10 +02:00
}
2019-06-08 00:11:08 +02:00
kvStore ( ) {
if ( ! this . kvStore _ ) throw new Error ( 'DecryptionWorker.kvStore_ is not set!!' ) ;
return this . kvStore _ ;
}
2017-12-14 21:39:13 +02:00
async scheduleStart ( ) {
if ( this . scheduleId _ ) return ;
this . scheduleId _ = setTimeout ( ( ) => {
this . scheduleId _ = null ;
2017-12-26 12:38:53 +02:00
this . start ( {
2018-10-08 20:11:53 +02:00
masterKeyNotLoadedHandler : 'dispatch' ,
2017-12-26 12:38:53 +02:00
} ) ;
2017-12-14 21:39:13 +02:00
} , 1000 ) ;
}
2019-06-08 00:11:08 +02:00
async decryptionDisabledItems ( ) {
let items = await this . kvStore ( ) . searchByPrefix ( 'decrypt:' ) ;
items = items . filter ( item => item . value > this . maxDecryptionAttempts _ ) ;
items = items . map ( item => {
const s = item . key . split ( ':' ) ;
return {
type _ : Number ( s [ 1 ] ) ,
id : s [ 2 ] ,
} ;
} ) ;
return items ;
}
async clearDisabledItem ( typeId , itemId ) {
await this . kvStore ( ) . deleteValue ( 'decrypt:' + typeId + ':' + itemId ) ;
}
2018-06-10 18:43:24 +02:00
dispatchReport ( report ) {
const action = Object . assign ( { } , report ) ;
action . type = 'DECRYPTION_WORKER_SET' ;
this . dispatch ( action ) ;
}
2017-12-26 12:38:53 +02:00
async start ( options = null ) {
if ( options === null ) options = { } ;
2018-10-08 20:11:53 +02:00
if ( ! ( 'masterKeyNotLoadedHandler' in options ) ) options . masterKeyNotLoadedHandler = 'throw' ;
2019-06-08 00:11:08 +02:00
if ( ! ( 'errorHandler' in options ) ) options . errorHandler = 'log' ;
2017-12-26 12:38:53 +02:00
2018-03-09 22:59:12 +02:00
if ( this . state _ !== 'idle' ) {
2017-12-26 12:38:53 +02:00
this . logger ( ) . info ( 'DecryptionWorker: cannot start because state is "' + this . state _ + '"' ) ;
return ;
}
2019-05-28 23:05:11 +02:00
const loadedMasterKeyCount = await this . encryptionService ( ) . loadedMasterKeysCount ( ) ;
if ( ! loadedMasterKeyCount ) {
this . logger ( ) . info ( 'DecryptionWorker: cannot start because no master key is currently loaded.' ) ;
const ids = await MasterKey . allIds ( ) ;
if ( ids . length ) {
this . dispatch ( {
type : 'MASTERKEY_SET_NOT_LOADED' ,
ids : ids ,
} ) ;
}
return ;
}
2018-03-09 22:59:12 +02:00
this . logger ( ) . info ( 'DecryptionWorker: starting decryption...' ) ;
2017-12-14 19:58:10 +02:00
2018-03-09 22:59:12 +02:00
this . state _ = 'started' ;
2017-12-14 20:53:08 +02:00
let excludedIds = [ ] ;
2018-06-10 18:43:24 +02:00
this . dispatchReport ( { state : 'started' } ) ;
2017-12-14 21:39:13 +02:00
try {
2018-01-28 19:37:29 +02:00
const notLoadedMasterKeyDisptaches = [ ] ;
2017-12-14 21:39:13 +02:00
while ( true ) {
const result = await BaseItem . itemsThatNeedDecryption ( excludedIds ) ;
const items = result . items ;
for ( let i = 0 ; i < items . length ; i ++ ) {
const item = items [ i ] ;
2018-01-31 21:51:29 +02:00
2017-12-14 21:39:13 +02:00
const ItemClass = BaseItem . itemClass ( item ) ;
2018-06-10 18:43:24 +02:00
this . dispatchReport ( {
itemIndex : i ,
itemCount : items . length ,
} ) ;
2019-06-08 00:11:08 +02:00
const counterKey = 'decrypt:' + item . type _ + ':' + item . id ;
const clearDecryptionCounter = async ( ) => {
await this . kvStore ( ) . deleteValue ( counterKey ) ;
}
2018-03-09 22:59:12 +02:00
2018-03-05 20:21:42 +02:00
// Don't log in production as it results in many messages when importing many items
2019-05-28 23:05:11 +02:00
// this.logger().debug('DecryptionWorker: decrypting: ' + item.id + ' (' + ItemClass.tableName() + ')');
2017-12-14 21:39:13 +02:00
try {
2019-06-08 00:11:08 +02:00
const decryptCounter = await this . kvStore ( ) . incValue ( counterKey ) ;
if ( decryptCounter > this . maxDecryptionAttempts _ ) {
this . logger ( ) . warn ( 'DecryptionWorker: ' + item . id + ' decryption has failed more than 2 times - skipping it' ) ;
excludedIds . push ( item . id ) ;
continue ;
}
2019-05-22 16:56:07 +02:00
const decryptedItem = await ItemClass . decrypt ( item ) ;
2019-06-08 00:11:08 +02:00
await clearDecryptionCounter ( ) ;
2019-05-22 16:56:07 +02:00
if ( decryptedItem . type _ === Resource . modelType ( ) && ! ! decryptedItem . encryption _blob _encrypted ) {
// itemsThatNeedDecryption() will return the resource again if the blob has not been decrypted,
// but that will result in an infinite loop if the blob simply has not been downloaded yet.
// So skip the ID for now, and the service will try to decrypt the blob again the next time.
excludedIds . push ( decryptedItem . id ) ;
}
if ( decryptedItem . type _ === Resource . modelType ( ) && ! decryptedItem . encryption _blob _encrypted ) {
this . eventEmitter _ . emit ( 'resourceDecrypted' , { id : decryptedItem . id } ) ;
}
2019-05-28 23:05:11 +02:00
if ( decryptedItem . type _ === Resource . modelType ( ) && ! decryptedItem . encryption _applied && ! ! decryptedItem . encryption _blob _encrypted ) {
this . eventEmitter _ . emit ( 'resourceMetadataButNotBlobDecrypted' , { id : decryptedItem . id } ) ;
}
2017-12-14 21:39:13 +02:00
} catch ( error ) {
2018-02-14 17:28:56 +02:00
excludedIds . push ( item . id ) ;
2019-05-22 16:56:07 +02:00
2018-10-08 20:11:53 +02:00
if ( error . code === 'masterKeyNotLoaded' && options . masterKeyNotLoadedHandler === 'dispatch' ) {
2018-01-28 19:37:29 +02:00
if ( notLoadedMasterKeyDisptaches . indexOf ( error . masterKeyId ) < 0 ) {
this . dispatch ( {
2018-03-09 22:59:12 +02:00
type : 'MASTERKEY_ADD_NOT_LOADED' ,
2018-01-28 19:37:29 +02:00
id : error . masterKeyId ,
} ) ;
notLoadedMasterKeyDisptaches . push ( error . masterKeyId ) ;
}
2019-06-08 00:11:08 +02:00
await clearDecryptionCounter ( ) ;
2017-12-14 21:39:13 +02:00
continue ;
}
2018-02-26 21:25:54 +02:00
2018-10-08 20:11:53 +02:00
if ( error . code === 'masterKeyNotLoaded' && options . masterKeyNotLoadedHandler === 'throw' ) {
2019-06-08 00:11:08 +02:00
await clearDecryptionCounter ( ) ;
2018-02-26 21:25:54 +02:00
throw error ;
}
2019-06-08 00:11:08 +02:00
if ( options . errorHandler === 'log' ) {
this . logger ( ) . warn ( 'DecryptionWorker: error for: ' + item . id + ' (' + ItemClass . tableName ( ) + ')' , error , item ) ;
} else {
throw error ;
}
2017-12-14 20:53:08 +02:00
}
}
2017-12-14 21:39:13 +02:00
if ( ! result . hasMore ) break ;
}
} catch ( error ) {
2018-03-09 22:59:12 +02:00
this . logger ( ) . error ( 'DecryptionWorker:' , error ) ;
this . state _ = 'idle' ;
2018-06-10 18:43:24 +02:00
this . dispatchReport ( { state : 'idle' } ) ;
2017-12-26 12:38:53 +02:00
throw error ;
2017-12-14 20:53:08 +02:00
}
2017-12-14 21:39:13 +02:00
2019-05-12 16:53:42 +02:00
// 2019-05-12: Temporary to set the file size of the resources
// that weren't set in migration/20.js due to being on the sync target
await ResourceService . autoSetFileSizes ( ) ;
2018-03-09 22:59:12 +02:00
this . logger ( ) . info ( 'DecryptionWorker: completed decryption.' ) ;
2017-12-26 12:38:53 +02:00
2019-05-22 16:56:07 +02:00
const downloadedButEncryptedBlobCount = await Resource . downloadedButEncryptedBlobCount ( ) ;
2018-03-09 22:59:12 +02:00
this . state _ = 'idle' ;
2019-05-22 16:56:07 +02:00
2019-05-28 23:05:11 +02:00
this . dispatchReport ( { state : 'idle' } ) ;
2019-05-22 16:56:07 +02:00
if ( downloadedButEncryptedBlobCount ) {
this . logger ( ) . info ( 'DecryptionWorker: Some resources have been downloaded but are not decrypted yet. Scheduling another decryption. Resource count: ' + downloadedButEncryptedBlobCount ) ;
this . scheduleStart ( ) ;
}
2017-12-14 19:58:10 +02:00
}
2018-03-09 22:59:12 +02:00
2017-12-14 19:58:10 +02:00
}
2018-03-09 22:59:12 +02:00
module . exports = DecryptionWorker ;