diff --git a/html/browser_action/views/settings.html b/html/browser_action/views/settings.html index 51cee82..35b09a9 100644 --- a/html/browser_action/views/settings.html +++ b/html/browser_action/views/settings.html @@ -52,6 +52,9 @@ Disable auto fill of forms + + Disable password picker + diff --git a/js/background/inject/inject.js b/js/background/inject/inject.js index 3e6b98e..638fd43 100644 --- a/js/background/inject/inject.js +++ b/js/background/inject/inject.js @@ -24,6 +24,7 @@ $j(document).ready(function () { } function enterLoginDetails(login) { + console.log('called', login) var username = (login.username.trim() !== '' ) ? login.username : login.email; fillPassword(username, login.password); @@ -32,6 +33,7 @@ $j(document).ready(function () { } } + _this.enterLoginDetails = enterLoginDetails; function setupAddCredentialFields() { var labelfield = $j('#savepw-label'); labelfield.val(document.title); @@ -269,10 +271,10 @@ $j(document).ready(function () { pickerButton.css('box-sizing', 'content-box'); pickerButton.css('position', 'absolute'); /* - pickerButton.css('background-color', 'rgb(234, 234, 234)'); - pickerButton.css('border-top-right-radius', el.css('border-top-right-radius')); - pickerButton.css('border-bottom-right-radius', el.css('border-bottom-right-radius')); - pickerButton.css('border-color', borderColor);*/ + pickerButton.css('background-color', 'rgb(234, 234, 234)'); + pickerButton.css('border-top-right-radius', el.css('border-top-right-radius')); + pickerButton.css('border-bottom-right-radius', el.css('border-bottom-right-radius')); + pickerButton.css('border-color', borderColor);*/ pickerButton.css('z-index', '999'); pickerButton.css('width', iconWidth); @@ -283,8 +285,8 @@ $j(document).ready(function () { pickerButton.css('height', height); pickerButton.css('margin', margin); pickerButton.css('font-weight', el.css('font-weight')); - pickerButton.css('top', Math.round((offset.top + (height / 4) - margin/2 - padding/2 ) - (borderHeight / 2))+ 'px'); - pickerButton.css('left', Math.round((offset.left + width*0.9) + paddingRight - padding + borderWidth ) + 'px'); + pickerButton.css('top', Math.round((offset.top + (height / 4) - margin / 2 - padding / 2 ) - (borderHeight / 2)) + 'px'); + pickerButton.css('left', Math.round((offset.left + width * 0.9) + paddingRight - padding + borderWidth) + 'px'); var onClick = function () { @@ -342,7 +344,7 @@ $j(document).ready(function () { password: pass }; //Disable password mining - $j(fields[1]).attr('type', 'hidden'); + //$j(fields[1]).attr('type', 'hidden'); API.runtime.sendMessage(API.runtime.id, {method: "minedForm", args: params}); } @@ -444,37 +446,44 @@ $j(document).ready(function () { insertFontCSS(); $j(document).unbind('click', togglePasswordPicker); checkForMined(); - var loginFields = getLoginFields(); - if (loginFields.length > 0) { - //@TODO prevent chrome from captuting pw's: http://stackoverflow.com/questions/27280461/prevent-chrome-from-prompting-to-save-password-from-input-box - for (var i = 0; i < loginFields.length; i++) { - var form = getFormFromElement(loginFields[i][0]); - createPasswordPicker(loginFields[i], form); + API.runtime.sendMessage(API.runtime.id, {method: 'getRuntimeSettings'}).then(function (result) { + var disablePasswordPicker = result.disablePasswordPicker; - //Password miner - $j(form).submit((function (loginFields) { - return function () { - formSubmitted(loginFields); + var loginFields = getLoginFields(); + if (loginFields.length > 0) { + //@TODO prevent chrome from captuting pw's: http://stackoverflow.com/questions/27280461/prevent-chrome-from-prompting-to-save-password-from-input-box + for (var i = 0; i < loginFields.length; i++) { + if(!disablePasswordPicker) { + var form = getFormFromElement(loginFields[i][0]); + createPasswordPicker(loginFields[i], form); } - })(loginFields[i])); - } - - var url = window.location.href; //@TODO use a extension function - API.runtime.sendMessage(API.runtime.id, { - method: "getCredentialsByUrl", - args: [url] - }).then(function (logins) { - //console.log('Found ' + logins.length + ' logins for this site'); - if (logins.length === 1) { - API.runtime.sendMessage(API.runtime.id, {method: 'isAutoFillEnabled'}).then(function (isEnabled) { - if(isEnabled){ - enterLoginDetails(logins[0]); + //Password miner + $j(form).submit((function (loginFields) { + return function () { + formSubmitted(loginFields); } - }); + })(loginFields[i])); } - }); - } + var url = window.location.href; //@TODO use a extension function + API.runtime.sendMessage(API.runtime.id, { + method: "getCredentialsByUrl", + args: [url] + }).then(function (logins) { + //console.log('Found ' + logins.length + ' logins for this site'); + if (logins.length === 1) { + API.runtime.sendMessage(API.runtime.id, {method: 'isAutoFillEnabled'}).then(function (isEnabled) { + if (isEnabled) { + enterLoginDetails(logins[0]); + } + }); + } + + }); + } + + }); + $j(document).click(togglePasswordPicker); $j(window).on('resize', function () { if (getLoginFields().length > 0) { diff --git a/js/background/service/background.js b/js/background/service/background.js index 08fef6e..a5decd7 100644 --- a/js/background/service/background.js +++ b/js/background/service/background.js @@ -335,9 +335,12 @@ return; } var tabUrl = tab.url; - var credentialAmount = getCredentialsByUrl(tabUrl).length; - - API.browserAction.setBadgeText({ + var logins = getCredentialsByUrl(tabUrl); + if(tab.active) { + window.contextMenu.setContextItems(logins); + } + var credentialAmount = logins.length; + API.browserAction.setBadgeText({ text: credentialAmount.toString(), tabId: tab.id }); @@ -392,6 +395,16 @@ } }); + API.tabs.onActivated.addListener(function () { + API.tabs.query({active: true, currentWindow: true}).then(function (tabs) { + if(master_password){ + createIconForTab(tabs[0]) + } else { + displayLogoutIcons(); + } + }); + }); + displayLogoutIcons(); storage.get('master_password').then(function (password) { diff --git a/js/background/service/contextMenu.js b/js/background/service/contextMenu.js new file mode 100644 index 0000000..3f9a113 --- /dev/null +++ b/js/background/service/contextMenu.js @@ -0,0 +1,130 @@ +/* global API */ + +/** + * Nextcloud - passman + * + * @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com) + * @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es) + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +window.contextMenu = (function () { + 'use strict'; + function initMenus() { + API.contextMenus.create({ + id: 'autoFill:', + title: 'Auto fill', + contexts: ['all'] + }); + + API.contextMenus.create({ + id: 'copy:User', + title: 'Copy username', + contexts: ['all'] + }); + + API.contextMenus.create({ + id: 'copy:Pass', + title: 'Copy password', + contexts: ['all'] + }); + + + API.contextMenus.create({ + id: 'copy:Url', + title: 'Copy URL', + contexts: ['all'] + }); + /* API.contextMenus.create({ + id: 'copy:OTP', + title: 'Copy OTP', + contexts: ['all'] + });*/ + } + + function createMenuItem(parentId, id, label, clickcb) { + API.contextMenus.create({ + id: id, + title: label, + contexts: ["all"], + parentId: parentId, + onclick: clickcb + }); + } + + function itemClickCallback(menu_action, login) { + var action = menu_action.menu.split(':', 1)[0]; + + if (action === 'copy') { + copyTextToClipboard(login[menu_action.field]); + return; + } + + if (action === 'autoFill') { + API.tabs.query({active: true, currentWindow: true}).then(function (tabs) { + API.tabs.sendMessage(tabs[0].id, {method: "enterLoginDetails", args: login}).then(function (response) { + }); + }); + } + } + + function copyTextToClipboard(text) { + var copyFrom = document.createElement("textarea"); + copyFrom.textContent = text; + var body = document.getElementsByTagName('body')[0]; + body.appendChild(copyFrom); + copyFrom.select(); + document.execCommand('copy'); + body.removeChild(copyFrom); + } + API.contextMenus.removeAll(); + initMenus(); + + return { + setContextItems: function (logins) { + + var fields = [ + {menu: 'autoFill:', field: 'autoFill'}, + {field: 'username', menu: 'copy:User'}, + {field: 'password', menu: 'copy:Pass'}, + {field: 'url', menu: 'copy:Url'}, + // {field: 'totp', menu: 'copy:OTP'} + ]; + API.contextMenus.removeAll(); + initMenus(); + + for (var i = 0; i < logins.length; i++) { + var login = logins[i]; + login.autoFill = true; + for (var f = 0; f < fields.length; f++) { + var field = fields[f]; + if (field['field'] === 'totp' && login.otp) { + login.totp = login.otp.secret; + } + if (login[field['field']]) { + createMenuItem(field['menu'], field['menu'] + ':' + login.guid, login.label, (function (field, login) { + return function () { + itemClickCallback(field, login); + }; + })(field, login)); + } + } + } + } + } + +}()); \ No newline at end of file diff --git a/js/lib/API/contextmenus.js b/js/lib/API/contextmenus.js new file mode 100644 index 0000000..1b808a1 --- /dev/null +++ b/js/lib/API/contextmenus.js @@ -0,0 +1,22 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + + +/* global API */ + +API.contextMenus = { + + create: API.api.contextMenus.create, + update: API.api.contextMenus.update, + remove: API.api.contextMenus.remove, + removeAll: API.api.contextMenus.removeAll, + + + /** + * Event handlers from now on + */ + onClicked: API.api.contextMenus.onClicked +}; \ No newline at end of file diff --git a/js/ui/popup/controllers/settings.js b/js/ui/popup/controllers/settings.js index 80751e9..3e6e52c 100644 --- a/js/ui/popup/controllers/settings.js +++ b/js/ui/popup/controllers/settings.js @@ -88,11 +88,7 @@ save_btn.show(); $scope.vaults = vaults; $scope.$apply(); - setTimeout(function () { - if($scope.settings.default_vault){ - jQuery('#defaultVault').find('[value="'+ $scope.settings.default_vault +'"]').attr('selected', 'selected'); - } - }); + }); }; @@ -112,14 +108,13 @@ $scope.saveSettings = function () { $scope.errors = []; var settings = angular.copy($scope.settings); - var v = getVaultByGuid(settings.default_vault); try{ - PAPI.decryptString(v.challenge_password, settings.vault_password); + PAPI.decryptString(settings.default_vault.challenge_password, settings.vault_password); } catch (e){ $scope.errors.push('Invalid vault key!'); return; } - settings.default_vault = v; + $scope.saving = true; API.runtime.sendMessage(API.runtime.id, {method: "saveSettings", args: settings}).then(function () { setTimeout(function () { diff --git a/js/ui/popup/controllers/setup.js b/js/ui/popup/controllers/setup.js index 2ed4a94..c8b97c1 100644 --- a/js/ui/popup/controllers/setup.js +++ b/js/ui/popup/controllers/setup.js @@ -48,7 +48,8 @@ refreshTime: 60, default_vault: {}, master_password: '', - disableAutoFill: false + disableAutoFill: false, + disablePasswordPicker: false }; $scope.vaults = []; diff --git a/manifest.json b/manifest.json index f4e3dd2..43755dc 100644 --- a/manifest.json +++ b/manifest.json @@ -28,7 +28,9 @@ "/js/lib/API/cookies.js", "/js/lib/API/browser_action.js", "/js/lib/API/tabs.js", + "/js/lib/API/contextmenus.js", "/js/lib/api.js", + "/js/background/service/contextMenu.js", "/js/background/service/background.js" ] }, @@ -42,6 +44,7 @@ "notifications", "tabs", "storage", + "contextMenus", "cookies" ], "content_scripts": [