Completed
Push — master ( 2b4a65...dae5f9 )
by Sander
39s
created

background.js ➔ saveCredential   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
c 2
b 0
f 0
nc 5
nop 1
dl 0
loc 25
rs 8.439
1
/* global API */
2
3
var background = (function () {
4
    var storage = new API.Storage();
5
    var _self = this;
6
    var _window = {};
7
8
9
    API.runtime.onConnect.addListener(function (port) {
10
11
        port.onMessage.addListener(function (msg) {
12
            if (msg === 'credential_amount') {
13
                port.postMessage('credential_amount:' + local_credentials.length);
14
            }
15
16
        });
17
18
    });
19
20
    var master_password = null;
21
22
    function getMasterPasswordSet() {
23
        return (master_password !== null);
24
    }
25
26
    _self.getMasterPasswordSet = getMasterPasswordSet;
27
28
    function setMasterPassword(opts) {
29
        master_password = opts.password;
30
        if (opts.hasOwnProperty('savePassword') && opts.savePassword === true) {
31
            // Save the password in plain text on user request.
32
            // No secure local storage is available :/
33
            storage.set('master_password', opts.password);
34
        } else {
35
            storage.set('master_password', null);
36
        }
37
38
        if (opts.password) {
39
            getSettings();
40
        } else {
41
            displayLogoutIcons();
42
        }
43
44
    }
45
46
    _self.setMasterPassword = setMasterPassword;
47
48
49
    var testMasterPasswordAgainst;
50
51
    function isMasterPasswordValid(password) {
52
        //return true;
53
        try {
54
            PAPI.decryptString(testMasterPasswordAgainst, password);
55
            return true;
56
        } catch (e) {
57
            return false;
58
        }
59
    }
60
61
    _self.isMasterPasswordValid = isMasterPasswordValid;
62
63
64
    var local_credentials = [];
65
    var local_vault = [];
66
    var encryptedFieldSettings = ['default_vault', 'nextcloud_host', 'nextcloud_username', 'nextcloud_password', 'vault_password'];
67
    _self.settings = {};
68
    _self.ticker = null;
69
    _self.running = false;
70
    function getSettings() {
71
72
        storage.get('settings').then(function (_settings) {
73
74
            if ((!_settings || !_settings.hasOwnProperty('nextcloud_host')) && !master_password) {
75
                API.tabs.create({
76
                    url: '/html/browser_action/browser_action.html'
77
                });
78
79
                return;
80
            }
81
82
            if (!master_password && _settings.hasOwnProperty('nextcloud_username') && _settings.hasOwnProperty('vault_password')) {
83
                _self.settings.isInstalled = 1;
84
                testMasterPasswordAgainst = _settings.nextcloud_username;
85
                return;
86
            }
87
88
            for (var i = 0; i < encryptedFieldSettings.length; i++) {
89
                var field = encryptedFieldSettings[i];
90
                _settings[field] = JSON.parse(PAPI.decryptString(_settings[field], master_password));
91
            }
92
93
94
            _self.settings = _settings;
95
96
            if (!_self.settings.hasOwnProperty('ignored_sites')) {
97
                _self.settings.ignored_sites = [];
98
            }
99
100
            if (!_self.settings.hasOwnProperty('disable_browser_autofill')) {
101
                _self.settings.disable_browser_autofill = true;
102
            }
103
            if (!_self.settings.hasOwnProperty('no_results_found_tab')) {
104
                _self.settings.no_results_found_tab = 'list';
105
            }
106
107
108
            PAPI.host = _settings.nextcloud_host;
109
            PAPI.username = _settings.nextcloud_username;
110
            PAPI.password = _settings.nextcloud_password;
111
            if (!_settings.vault_password) {
112
                return;
113
            }
114
            if (PAPI.credentialsSet()) {
115
                getCredentials();
116
                if (_self.running) {
117
                    clearInterval(_self.ticker);
118
                }
119
120
                _self.running = true;
121
                _self.ticker = setInterval(function () {
122
                    getCredentials();
123
                }, _self.settings.refreshTime * 1000);
124
            } else {
125
                console.log('Login details are missing!');
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
126
            }
127
        });
128
    }
129
130
    _self.getSettings = getSettings;
131
132
    function getRuntimeSettings() {
133
        return _self.settings;
134
    }
135
136
    _self.getRuntimeSettings = getRuntimeSettings;
137
138
    function getSetting(name) {
139
        return _self.settings[name];
140
    }
141
142
    _self.getSetting = getSetting;
143
144
    function saveSettings(settings, cb) {
0 ignored issues
show
Unused Code introduced by
The parameter cb is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
145
        for (var i = 0; i < encryptedFieldSettings.length; i++) {
146
            var field = encryptedFieldSettings[i];
147
            settings[field] = PAPI.encryptString(JSON.stringify(settings[field]), master_password);
148
        }
149
        PAPI.host = settings.nextcloud_host;
150
        PAPI.username = settings.nextcloud_username;
151
        PAPI.password = settings.nextcloud_password;
152
153
        if (!settings.hasOwnProperty('ignored_sites')) {
154
            settings.ignored_sites = [];
155
        }
156
157
        if (!settings.hasOwnProperty('disable_browser_autofill')) {
158
            settings.disable_browser_autofill = true;
159
        }
160
161
        if (!_self.settings.hasOwnProperty('password_picker_first_tab')) {
162
            _self.settings.disable_browser_autofill = 'list';
163
        }
164
165
        //window.settings contains the run-time settings
166
        _self.settings = settings;
167
168
169
        storage.set('settings', settings).then(function () {
170
            getSettings();
171
        });
172
173
    }
174
175
    _self.saveSettings = saveSettings;
176
177
178
    function getCredentials() {
179
        if (!master_password) {
180
            return;
181
        }
182
        //console.log('Loading vault with the following settings: ', settings);
183
        var tmpList = [];
184
        PAPI.getVault(_self.settings.default_vault.guid, function (vault) {
185
            if (vault.hasOwnProperty('error')) {
186
                return;
187
            }
188
            var _credentials = vault.credentials;
189
            for (var i = 0; i < _credentials.length; i++) {
190
                var key = _self.settings.vault_password;
191
                var credential = _credentials[i];
192
                if (credential.hidden === 1) {
193
                    continue;
194
                }
195
                var usedKey = key;
196
                //Shared credentials are not implemented yet
197
                if (credential.hasOwnProperty('shared_key') && credential.shared_key) {
198
                    usedKey = PAPI.decryptString(credential.shared_key, key);
199
200
                }
201
                credential = PAPI.decryptCredential(credential, usedKey);
202
                if (credential.delete_time === 0) {
203
                    tmpList.push(credential);
204
                }
205
206
            }
207
            delete vault.credentials;
208
            local_vault = vault;
209
            local_credentials = tmpList;
210
            updateTabsIcon();
211
        });
212
    }
213
214
    _self.getCredentials = getCredentials;
215
216
    function getCredentialsByUrl(_url, sender) {
0 ignored issues
show
Unused Code introduced by
The parameter sender is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
217
        if (!master_password) {
218
            return [];
219
        }
220
        if (!_url || _url === '') {
221
            return [];
222
        }
223
        var url = processURL(_url, _self.settings.ignoreProtocol, _self.settings.ignoreSubdomain, _self.settings.ignorePath, _self.settings.ignorePort);
224
        var found_list = [];
225
        for (var i = 0; i < local_credentials.length; i++) {
226
            if (local_credentials[i].url && local_credentials[i].username && local_credentials[i].password) {
227
                if (local_credentials[i].url.indexOf(url) !== -1) {
228
                    found_list.push(local_credentials[i]);
229
                }
230
            }
231
        }
232
        return found_list;
233
    }
234
235
    _self.getCredentialsByUrl = getCredentialsByUrl;
236
237
238
    function saveCredential(credential) {
239
        //@TODO save shared password
240
        if(credential.shared_key){
241
            return;
242
        }
243
        if (!credential.credential_id) {
244
            PAPI.createCredential(credential, _self.settings.vault_password, function (createdCredential) {
245
                local_credentials.push(createdCredential);
246
            });
247
        } else {
248
            var credential_index;
249
            for (var i = 0; i < local_credentials.length; i++) {
250
                if (local_credentials[i].guid === credential.guid) {
251
                    credential_index = i;
252
                    break;
253
                }
254
            }
255
256
            PAPI.updateCredential(credential, _self.settings.vault_password, function (updatedCredential) {
257
                if (credential_index) {
258
                    local_credentials[credential_index] = updatedCredential;
259
                }
260
            });
261
        }
262
    }
263
264
    _self.saveCredential = saveCredential;
265
266
    function getCredentialByGuid(guid) {
267
        for (var i = 0; i < local_credentials.length; i++) {
268
            var credential = local_credentials[i];
269
            if (credential.guid === guid) {
270
                return credential;
271
            }
272
        }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
273
    }
274
275
    _self.getCredentialByGuid = getCredentialByGuid;
276
277
    function getCredentialForHTTPAuth(req) {
278
        return getCredentialsByUrl(req.url)[0];
279
    }
280
281
    _window.getCredentialForHTTPAuth = getCredentialForHTTPAuth;
282
283
    var mined_data = [];
284
285
    function minedForm(data, sender) {
286
        var url = sender.url;
287
        var existingLogins = getCredentialsByUrl(sender.url);
288
        var title = API.i18n.getMessage('detected_new_login') + ':';
289
        var minedMatchingID = null;
290
        for (var j = 0; j < existingLogins.length; j++) {
291
            var login = existingLogins[j];
292
            if (login.username === data.username) {
293
                if (login.password !== data.password) {
294
                    minedMatchingID = login.guid;
295
                    title = API.i18n.getMessage('detected_changed_login') + ':';
296
                }
297
                else {
298
                    //console.log('No changes detected');
299
                    delete mined_data[sender.tab.id];
300
                    return;
301
                }
302
            }
303
        }
304
        mined_data[sender.tab.id] = {
305
            title: title,
306
            url: url,
307
            username: data.username,
308
            password: data.password,
309
            label: sender.title,
310
            guid: minedMatchingID
311
        };
312
313
        //console.log('Done mining, ', mined_data, sender.tab.id);
314
    }
315
316
    _self.minedForm = minedForm;
317
318
    function getMinedData(args, sender) {
319
        //console.log('Fecthing  mined data for tab id', sender.tab.id)
320
        var senderUrl = sender.tab.url;
321
        var site = processURL(senderUrl, _self.settings.ignoreProtocol, _self.settings.ignoreSubdomain, _self.settings.ignorePath, _self.settings.ignorePort);
322
        if (!_self.settings) {
323
            return null;
324
        }
325
        if (!_self.settings.hasOwnProperty('ignored_sites')) {
326
            return mined_data[sender.tab.id];
327
        }
328
        var matches = _self.settings.ignored_sites.filter(function (item) {
329
            return typeof item === 'string' && site.indexOf(item) > -1;
330
        });
331
332
        if (matches.length !== 0) {
333
            return null;
334
        }
335
        return mined_data[sender.tab.id];
336
    }
337
338
    _self.getMinedData = getMinedData;
339
340
    function clearMined(args, sender) {
341
        delete mined_data[sender.tab.id];
342
    }
343
344
    _self.clearMined = clearMined;
345
346
    function saveMinedCallback(args) {
347
        createIconForTab(args.sender.tab);
348
        API.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
349
            API.tabs.sendMessage(args.sender.tab.id, {method: "minedLoginSaved", args: args}).then(function (response) {
0 ignored issues
show
Unused Code introduced by
The parameter response is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
350
            });
351
        });
352
    }
353
354
    function ignoreSite(_url) {
355
        if (!_self.settings.hasOwnProperty('ignored_sites')) {
356
            _self.settings.ignored_sites = [];
357
        }
358
        var site = processURL(_url, _self.settings.ignoreProtocol, _self.settings.ignoreSubdomain, _self.settings.ignorePath, _self.settings.ignorePort);
359
        if (_self.settings.ignored_sites.indexOf(site) === -1) {
360
            _self.settings.ignored_sites.push(site);
361
            saveSettings(_self.settings);
362
        }
363
    }
364
365
    _self.ignoreSite = ignoreSite;
366
367
    function passToParent(args, sender) {
368
        API.tabs.sendMessage(sender.tab.id, {method: args.injectMethod, args: args.args}).then(function (response) {
0 ignored issues
show
Unused Code introduced by
The parameter response is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
369
        });
370
    }
371
372
    _self.passToParent = passToParent;
373
374
    function getActiveTab(opt) {
375
        API.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
376
            var tab = tabs[0];
377
            API.tabs.sendMessage(tab.id, {method: opt.returnFn, args: tab}).then(function (response) {
0 ignored issues
show
Unused Code introduced by
The parameter response is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
378
            });
379
        });
380
    }
381
382
    _self.getActiveTab = getActiveTab;
383
384
    function updateCredentialUrlDoorhanger(login) {
385
        API.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
386
            var tab = tabs[0];
387
            var data = login;
388
            data.url = tab.url;
389
            data.title = API.i18n.getMessage('detected_changed_url') + ':';
390
            API.tabs.sendMessage(tab.id, {
391
                method: 'showUrlUpdateDoorhanger',
392
                args: {data: data}
393
            });
394
        });
395
    }
396
397
    _self.updateCredentialUrlDoorhanger = updateCredentialUrlDoorhanger;
398
399
    function updateCredentialUrl(data, sender) {
400
        mined_data[sender.tab.id] = data;
401
        saveMined({}, sender);
402
403
    }
404
405
    _self.updateCredentialUrl = updateCredentialUrl;
406
407
    function saveMined(args, sender) {
408
        var data = mined_data[sender.tab.id];
409
        var credential,
410
            credential_index;
411
412
        if (data.guid === null) {
413
            credential = PAPI.newCredential();
414
        } else {
415
            for (var i = 0; i < local_credentials.length; i++) {
416
                if (local_credentials[i].guid === data.guid) {
417
                    credential = local_credentials[i];
418
                    credential_index = i;
419
                    break;
420
                }
421
            }
422
        }
423
        credential.username = data.username;
0 ignored issues
show
Bug introduced by
The variable credential seems to not be initialized for all possible execution paths.
Loading history...
424
        credential.password = data.password;
425
        credential.url = sender.tab.url;
426
        if (credential.guid !== null) {
427
            PAPI.updateCredential(credential, _self.settings.vault_password, function (updatedCredential) {
428
                if (credential_index) {
429
                    local_credentials[credential_index] = updatedCredential;
430
                }
431
                saveMinedCallback({credential: credential, updated: true, sender: sender});
0 ignored issues
show
Bug introduced by
The variable credential seems to not be initialized for all possible execution paths.
Loading history...
432
                delete mined_data[sender.tab.id];
433
            });
434
        } else {
435
            credential.label = sender.tab.title;
436
            credential.vault_id = local_vault.vault_id;
437
            PAPI.createCredential(credential, _self.settings.vault_password, function (createdCredential) {
438
                saveMinedCallback({credential: credential, updated: false, sender: sender});
0 ignored issues
show
Bug introduced by
The variable credential seems to not be initialized for all possible execution paths.
Loading history...
439
                local_credentials.push(createdCredential);
440
                delete mined_data[sender.tab.id];
441
            });
442
        }
443
    }
444
445
    _self.saveMined = saveMined;
446
447
    function searchCredential(searchText) {
448
        searchText = searchText.toLowerCase();
449
        var searchFields = ['label', 'username', 'email', 'url', 'description'];
450
        var results = [];
451
        for (var i = 0; i < local_credentials.length; i++) {
452
            var credential = local_credentials[i];
453
            for (var f = 0; f < searchFields.length; f++) {
454
                var field = searchFields[f];
455
                if(!credential[field]){
456
                    continue;
457
                }
458
459
                var field_value = credential[field].toLowerCase();
460
                if (field_value.indexOf(searchText) !== -1) {
461
                    results.push(credential);
462
                    break;
463
                }
464
            }
465
        }
466
        return results;
467
    }
468
469
    _self.searchCredential = searchCredential;
470
471
472
    function injectCreateCredential(args, sender) {
473
        var credential = PAPI.newCredential();
474
        credential.label = args.label;
475
        credential.username = args.username;
476
        credential.password = args.password;
477
        credential.vault_id = local_vault.vault_id;
478
        credential.url = sender.tab.url;
479
        PAPI.createCredential(credential, _self.settings.vault_password, function (createdCredential) {
480
            saveMinedCallback({credential: credential, updated: false, sender: sender, selfAdded: true});
481
            local_credentials.push(createdCredential);
482
483
        });
484
    }
485
486
    self.injectCreateCredential = injectCreateCredential;
0 ignored issues
show
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
487
488
    function isVaultKeySet() {
489
        return (_self.settings.vault_password !== null);
490
    }
491
492
    _self.isVaultKeySet = isVaultKeySet;
493
494
    function isAutoFillEnabled() {
495
        if (!_self.settings.hasOwnProperty('disableAutoFill')) {
496
            return true;
497
        }
498
        return (_self.settings.disableAutoFill === false);
499
    }
500
501
    _self.isAutoFillEnabled = isAutoFillEnabled;
502
503
    var doorhangerData = null;
504
505
    function setDoorhangerData(data) {
506
        doorhangerData = data;
507
    }
508
509
    _self.setDoorhangerData = setDoorhangerData;
510
511
    function getDoorhangerData() {
512
        return doorhangerData;
513
    }
514
515
    _self.getDoorhangerData = getDoorhangerData;
516
517
    function closeSetupTab() {
518
        API.tabs.query({url: 'chrome-extension://'+ API.runtime.id +'/html/browser_action/browser_action.html'}).then(function (tabs) {
519
           if(tabs) {
520
               API.tabs.remove(tabs[0].id);
521
           }
522
        });
523
    }
524
    _self.closeSetupTab = closeSetupTab;
525
526
    API.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
527
528
        if (!msg || !msg.hasOwnProperty('method')) {
529
            return;
530
        }
531
        var result = false;
532
        if (_self[msg.method]) {
533
            result = _self[msg.method](msg.args, sender);
534
        } else {
535
            console.warn('[NOT FOUND] Method call', msg.method, 'args: ', msg.args);
536
        }
537
538
        sendResponse(result);
539
    });
540
541
    var defaultColor = '#0082c9';
542
543
    function createIconForTab(tab) {
544
        if (!master_password) {
545
            return;
546
        }
547
        var tabUrl = tab.url;
548
        var logins = getCredentialsByUrl(tabUrl);
549
        if (tab.active) {
550
            window.contextMenu.setContextItems(logins);
551
        }
552
        var credentialAmount = logins.length;
553
        API.browserAction.setBadgeText({
554
            text: credentialAmount.toString(),
555
            tabId: tab.id
556
        });
557
        API.browserAction.setBadgeBackgroundColor({
558
            color: defaultColor,
559
            tabId: tab.id
560
        });
561
562
        var plural = (credentialAmount === 1) ? API.i18n.getMessage('credential') : API.i18n.getMessage('credentials');
563
        API.browserAction.setTitle({
564
            title: API.i18n.getMessage('browser_action_title_login', [credentialAmount.toString(), plural.toString()]),
565
            tabId: tab.id
566
        });
567
    }
568
569
    function displayLogoutIcons() {
570
        if (_self.settings) {
571
            API.tabs.query({}).then(function (tabs) {
572
                for (var t = 0; t < tabs.length; t++) {
573
                    var tab = tabs[t];
574
                    API.browserAction.setBadgeText({
575
                        text: '🔑',
576
                        tabId: tab.id
577
                    });
578
                    API.browserAction.setBadgeBackgroundColor({
579
                        color: '#ff0000',
580
                        tabId: tab.id
581
                    });
582
                    API.browserAction.setTitle({
583
                        title: API.i18n.getMessage('browser_action_title_locked'),
584
                        tabId: tab.id
585
                    });
586
                }
587
            });
588
        }
589
    }
590
591
    function updateTabsIcon() {
592
        API.tabs.query({}).then(function (tabs) {
593
            for (var t = 0; t < tabs.length; t++) {
594
                var tab = tabs[t];
595
                createIconForTab(tab);
596
            }
597
        });
598
    }
599
600
601
    API.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
602
        if (master_password) {
603
            createIconForTab(tab);
604
        } else {
605
            displayLogoutIcons();
606
        }
607
    });
608
609
    API.tabs.onActivated.addListener(function () {
610
        API.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
611
            if (master_password) {
612
                createIconForTab(tabs[0]);
613
            } else {
614
                displayLogoutIcons();
615
            }
616
        });
617
    });
618
619
    displayLogoutIcons();
620
621
622
623
    storage.get('master_password').then(function (password) {
624
        if (password) {
625
            master_password = password;
626
            API.api.browserAction.setBadgeBackgroundColor({
627
                color: defaultColor
628
            });
629
        }
630
        getSettings();
631
    }).error(function (error) {
632
        if (error === "Data not found") {
633
            getSettings();
634
        }
635
    });
636
    return _window;
637
}());
638
639