mirror of
https://github.com/nextcloud/passman.git
synced 2025-10-02 01:34:26 +08:00
Lock vault after 3 wrong attempts (Fixes #197)
Fix share button, fix shared_key not added to storedCredential after sharing (Fixes #249) Add password app importer. Fixes #248 Fix version check via proxy. Fixes #237 Fix activity app not filtering. Fixes #246 Add EnPass txt importer. Fixes #159 Fix for disabled share button Require vault key for export. Fixes #199 Indicate that sharing only works with users that have 1 or more vaults. Fixes #242 Reset tags on logout. Fixes #245 Ability to enter OTP secret manually. Fixes #198 Create teampass importer. Fixes #244
This commit is contained in:
parent
76852c0d77
commit
6767322a22
40 changed files with 619 additions and 141 deletions
|
@ -8,7 +8,7 @@ To make it possible for us to help you please fill out below information careful
|
|||
If you like to make a feature request, please remove the issue template.
|
||||
If you want to fill in a issue, remove the feature request template.
|
||||
-->
|
||||
##Issue
|
||||
## Bug report
|
||||
|
||||
### Steps to reproduce
|
||||
1.
|
||||
|
@ -41,10 +41,8 @@ Tell us what happens instead
|
|||
**cloud server:** Nextcloud or ownCloud
|
||||
|
||||
**cloud version:** (see admin page or version.php)
|
||||
|
||||
|
||||
|
||||
|
||||
#### Browser log
|
||||
<details>
|
||||
<summary>Browser log</summary>
|
||||
|
|
135
README.md
135
README.md
|
@ -1,4 +1,5 @@
|
|||
#Passman
|
||||
Passman is a full featured password manager.
|
||||
|
||||
[](https://travis-ci.org/nextcloud/passman)
|
||||
[](https://www.codacy.com/app/brantje/passman?utm_source=github.com&utm_medium=referral&utm_content=nextcloud/passman&utm_campaign=Badge_Grade)
|
||||
|
@ -6,43 +7,19 @@
|
|||
[](https://scrutinizer-ci.com/g/nextcloud/passman/?branch=master)
|
||||
|
||||
|
||||
Passman is a full featured password manager.
|
||||
Features:
|
||||
- Vaults
|
||||
- Vault key is never sent to the server
|
||||
- Credentials are stored with 256 bit AES
|
||||
- Ability to add custom fields to credentials
|
||||
- Built-in OTP(One Time Password) generator
|
||||
- Password analyzer
|
||||
- Share passwords internally and via link in a secure manner.
|
||||
- Import from various password managers:
|
||||
- KeePass
|
||||
- LastPass
|
||||
- DashLane
|
||||
- ZOHO
|
||||
- Clipperz.is
|
||||
## Contents
|
||||
* [Screenshots](https://github.com/nextcloud/passman#Screenshots)
|
||||
* [Features](https://github.com/nextcloud/passman#features)
|
||||
* [External apps](https://github.com/nextcloud/passman#external-apps)
|
||||
* [Security](https://github.com/nextcloud/passman#security)
|
||||
* [Password generation](https://github.com/nextcloud/passman#password-generation)
|
||||
* [Storing credentials](https://github.com/nextcloud/passman#storing-credentials)
|
||||
* [API](https://github.com/nextcloud/passman#api)
|
||||
* [Docker](https://github.com/nextcloud/passman#docker)
|
||||
* [Maintainers](https://github.com/nextcloud/passman#main-developers)
|
||||
* [Contributors](https://github.com/nextcloud/passman#contributors)
|
||||
|
||||
|
||||
For a demo of this app visit [https://demo.passman.cc](https://demo.passman.cc)
|
||||
|
||||
## Tested on
|
||||
- NextCloud 10 / 11
|
||||
- ownCloud 9.1+
|
||||
|
||||
## Browser extensions
|
||||
At this moment we don't have any extensions available, sorry!
|
||||
However, if you're interested in helping us (or you want to guide us while building)
|
||||
Check out: https://github.com/nextcloud/passman-chrome-extension
|
||||
|
||||
|
||||
## Supported databases
|
||||
- SQL Lite*
|
||||
- MySQL / MariaDB*
|
||||
|
||||
*Tested on travis
|
||||
|
||||
Untested databases:
|
||||
- pgsql
|
||||
|
||||
|
||||
##Screenshots
|
||||
|
@ -56,15 +33,85 @@ Untested databases:
|
|||
|
||||
For more screenshots: [Click here](http://imgur.com/a/giKVt)
|
||||
|
||||
## Encryption (server side)
|
||||
All passwords are encrypted client side AND server side.
|
||||
This means that if you move to another server you have to backup the following from config.php
|
||||
- `passwordsalt`
|
||||
- `secret`
|
||||
|
||||
## Features:
|
||||
- Vaults
|
||||
- Vault key is never sent to the server
|
||||
- Credentials are stored with 256 bit AES (see [security](https://github.com/nextcloud/passman#security))
|
||||
- Ability to add custom fields to credentials
|
||||
- Built-in OTP(One Time Password) generator
|
||||
- Password analyzer
|
||||
- Share passwords internally and via link in a secure manner.
|
||||
- Import from various password managers:
|
||||
- KeePass
|
||||
- LastPass
|
||||
- DashLane
|
||||
- ZOHO
|
||||
- Clipperz.is
|
||||
- EnPass
|
||||
- [ocPasswords](https://github.com/fcturner/passwords)
|
||||
|
||||
|
||||
For a demo of this app visit [https://demo.passman.cc](https://demo.passman.cc)
|
||||
|
||||
## Tested on
|
||||
- NextCloud 10 / 11
|
||||
- ownCloud 9.1+
|
||||
|
||||
|
||||
## External apps
|
||||
- [Firefox / chrome extension](https://github.com/nextcloud/passman-webextension)
|
||||
- [Android app](https://github.com/nextcloud/passman-android)
|
||||
|
||||
|
||||
## Supported databases
|
||||
- SQL Lite*
|
||||
- MySQL / MariaDB*
|
||||
|
||||
*Tested on travis
|
||||
|
||||
Untested databases:
|
||||
- pgsql
|
||||
|
||||
## Security
|
||||
|
||||
### Password generation
|
||||
Passman features a build in password generator.
|
||||
Not it only generates passwords, but it also measures their strength using [zxcvbn](https://github.com/dropbox/zxcvbn).
|
||||

|
||||
|
||||
Generate passwords as you like
|
||||

|
||||
Passwords are generated using the random functions from `sjcl`.
|
||||
|
||||
|
||||
### Storing credentials
|
||||
All passwords are encrypted client side using [sjcl](https://github.com/bitwiseshiftleft/sjcl) which uses AES-256 bit.
|
||||
Users supply a vault key which is feed into sjcl as encryption key.
|
||||
After the credentials are encrypted they are send to the server, there they will be encrypted again.
|
||||
This time using the following routine:
|
||||
- A key is generated using `passwordsalt` and `secret` from config.php *so back those up*
|
||||
- Then the key is [stretched](http://en.wikipedia.org/wiki/Key_stretching) using [Password-Based Key Derivation Function 2](http://en.wikipedia.org/wiki/PBKDF2) (PBKDF2).
|
||||
- [Encrypt-then-MAC](http://en.wikipedia.org/wiki/Authenticated_encryption#Approaches_to_Authenticated_Encryption) (EtM) is used for ensuring the authenticity of the encrypted data.
|
||||
- Uses openssl with the `aes-256-cbc` ciper.
|
||||
- [Initialization vector](http://en.wikipedia.org/wiki/Initialization_vector) (IV) is hidden
|
||||
- [Double Hash-based Message Authentication Code](http://en.wikipedia.org/wiki/Hash-based_message_authentication_code) (HMAC) is applied for verification of the source data.
|
||||
|
||||
|
||||
### Sharing credentials.
|
||||
Passman allows users to share passwords (this can be turned off by an administrator).
|
||||
|
||||
|
||||
|
||||
## API
|
||||
For developers passman offers an [api](https://github.com/nextcloud/passman/wiki/API).
|
||||
|
||||
## Support Passman
|
||||
If you like passman, support us via [patreon](https://www.patreon.com/user?u=4833592) or [paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6YS8F97PETVU2)
|
||||
|
||||
Passman is open source, but we would gladly accept a beer (or pizza!)
|
||||
Please consider donating via
|
||||
- [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6YS8F97PETVU2)
|
||||
- [Patreon](https://www.patreon.com/user?u=4833592)
|
||||
- Bitcoin: 1H2c5tkGX54n48yEtM4Wm4UrAGTW85jQpe
|
||||
|
||||
## Code reviews
|
||||
If you have any improvements regarding our code.
|
||||
|
@ -77,8 +124,8 @@ Please do the following
|
|||
Or if you're feeling lazy, create an issue, and we'll think about it.
|
||||
|
||||
## Docker
|
||||
To run passman with docker you can use `docker run -p 8080:80 -p 8443:443 brantje/passman`
|
||||
To use your own SSL cert mount them as below
|
||||
To run passman with docker you can use `docker run -p 8080:80 -p 8443:443 brantje/passman`
|
||||
You have to supply your own ssl certs.
|
||||
Example:
|
||||
`docker run -p 8080:80 -p 8443:443 -v /directory/cert.pem:/data/ssl/cert.pem -v /directory/cert.key:/data/ssl/cert.key brantje/passman`
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ $manager->registerNotifier(function() {
|
|||
$manager = \OC::$server->getActivityManager();
|
||||
$manager->registerExtension(function() {
|
||||
return new Activity(
|
||||
\OC::$server->getL10NFactory()
|
||||
\OC::$server->getURLGenerator()
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ For an demo of this app visit [https://demo.passman.cc](https://demo.passman.cc)
|
|||
]]></description>
|
||||
|
||||
<licence>AGPL</licence>
|
||||
<version>2.0.2</version>
|
||||
<version>2.1.0</version>
|
||||
<author homepage="https://github.com/brantje">Sander Brand</author>
|
||||
<author homepage="https://github.com/animalillo">Marcos Zuriaga</author>
|
||||
<namespace>Passman</namespace>
|
||||
|
|
|
@ -69,6 +69,7 @@ class TranslationController extends ApiController {
|
|||
|
||||
// js/app/controllers/import.js
|
||||
'import.file.read' => $this->trans->t('File read successfully!'),
|
||||
'import.steps' => $this->trans->t('Follow the following steps to import your file'),
|
||||
|
||||
'import.no.label' => $this->trans->t('Credential has no label, skipping'),
|
||||
'import.adding' => $this->trans->t('Adding {{credential}}'),
|
||||
|
@ -144,7 +145,7 @@ class TranslationController extends ApiController {
|
|||
|
||||
|
||||
// templates/views/partials/edit_credential/otp.html
|
||||
'upload.qr' => $this->trans->t('Upload your OTP qr code'),
|
||||
'upload.qr' => $this->trans->t('Upload or enter your OTP secret'),
|
||||
'current.qr' => $this->trans->t('Current OTP settings'),
|
||||
'issuer' => $this->trans->t('Issuer'),
|
||||
'secret' => $this->trans->t('Secret'),
|
||||
|
@ -173,6 +174,7 @@ class TranslationController extends ApiController {
|
|||
// templates/views/partials/forms/settings/export.html
|
||||
'export.type' => $this->trans->t('Export type'),
|
||||
'export' => $this->trans->t('Export'),
|
||||
'export.confirm.text' => $this->trans->t('Enter vault password to confirm export.'),
|
||||
|
||||
// templates/views/partials/forms/settings/general_settings.html
|
||||
'rename.vault' => $this->trans->t('Rename vault'),
|
||||
|
@ -226,6 +228,7 @@ class TranslationController extends ApiController {
|
|||
|
||||
// templates/vieuws/partials/forms/share_credential/basics.html
|
||||
'search.u.g' => $this->trans->t('Search users or groups...'),
|
||||
'search.result.missing' => $this->trans->t('Missing users? Only users that have vaults are shown.'),
|
||||
'cyphering' => $this->trans->t('Cyphering'),
|
||||
'uploading' => $this->trans->t('Uploading'),
|
||||
'user' => $this->trans->t('User'),
|
||||
|
@ -325,6 +328,7 @@ class TranslationController extends ApiController {
|
|||
'accept' => $this->trans->t('Accept'),
|
||||
'decline' => $this->trans->t('Decline'),
|
||||
'session.time.left' => $this->trans->t('You have {{session_time}} left before logout.'),
|
||||
'vault.locked' => $this->trans->t('Your vault has been locked for {{time}} because of {{tries}} failed attempts!'),
|
||||
|
||||
// templates/views/vaults.html
|
||||
'last.access' => $this->trans->t('Last accessed'),
|
||||
|
|
21
css/app.css
21
css/app.css
|
@ -326,6 +326,8 @@
|
|||
display: inline-block; }
|
||||
.vault_wrapper .login_form .button {
|
||||
margin-top: 10px; }
|
||||
.vault_wrapper .login_opts {
|
||||
margin-bottom: 10px; }
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.vault_wrapper {
|
||||
|
@ -637,6 +639,8 @@
|
|||
#app-content #app-content-wrapper .edit_credential .custom_fields table tr td, #app-content #app-content-wrapper .edit_credential .files table tr td {
|
||||
height: 50px;
|
||||
vertical-align: middle; }
|
||||
#app-content #app-content-wrapper .edit_credential .otpText {
|
||||
padding-right: 10px; }
|
||||
#app-content #app-content-wrapper .app_sidebar {
|
||||
padding: 10px;
|
||||
overflow-y: auto; }
|
||||
|
@ -882,6 +886,12 @@
|
|||
.tab_container.settings {
|
||||
margin-bottom: 50px; }
|
||||
|
||||
.import-steps {
|
||||
padding-left: 16px;
|
||||
margin-bottom: 10px; }
|
||||
.import-steps li {
|
||||
list-style-type: disc; }
|
||||
|
||||
#app-settings-content:not(.ng-hide) {
|
||||
height: 90px;
|
||||
display: inherit !important;
|
||||
|
@ -943,4 +953,15 @@
|
|||
input[type="checkbox"] {
|
||||
min-height: inherit; }
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px; }
|
||||
|
||||
.alert-danger {
|
||||
color: #a94442;
|
||||
background-color: #f2dede;
|
||||
border-color: #ebccd1; }
|
||||
|
||||
/*# sourceMappingURL=app.css.map */
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -72,7 +72,7 @@
|
|||
url: 'views/partials/forms/edit_credential/basics.html',
|
||||
color: 'blue'
|
||||
};
|
||||
|
||||
$scope.otpType = 'qrcode';
|
||||
$translate(['general', 'password', 'custom.fields','files','otp']).then(function (translations) {
|
||||
$scope.tabs = [{
|
||||
title: translations.general,
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
.controller('ExportCtrl', ['$scope', '$window', 'CredentialService', 'VaultService', '$translate', function ($scope, $window, CredentialService, VaultService, $translate) {
|
||||
$scope.available_exporters = [];
|
||||
$scope.active_vault = VaultService.getActiveVault();
|
||||
|
||||
$scope.confirm_key = '';
|
||||
|
||||
$scope.$watch(function () {
|
||||
return $window.PassmanExporter;
|
||||
|
@ -58,6 +58,13 @@
|
|||
|
||||
|
||||
$scope.startExport = function () {
|
||||
$scope.error = false;
|
||||
if(VaultService.getActiveVault().vaultKey !== $scope.confirm_key){
|
||||
var msg = $translate.instant('invalid.vault.key');
|
||||
$scope.error = msg;
|
||||
_log(msg);
|
||||
return;
|
||||
}
|
||||
_log($translate.instant('export.starting'));
|
||||
var _credentials = [];
|
||||
VaultService.getVault(VaultService.getActiveVault()).then(function (vault) {
|
||||
|
@ -67,7 +74,8 @@
|
|||
for (var i = 0; i < vault.credentials.length; i++) {
|
||||
var _credential = angular.copy(vault.credentials[i]);
|
||||
if (_credential.hidden === 0) {
|
||||
_credential = CredentialService.decryptCredential(_credential);
|
||||
var key = CredentialService.getSharedKeyFromCredential(_credential);
|
||||
_credential = CredentialService.decryptCredential(_credential, key);
|
||||
_credentials.push(_credential);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
function ($scope, VaultService, $location, $rootScope, TagService, SettingsService) {
|
||||
$rootScope.logout = function () {
|
||||
SettingsService.setSetting('defaultVaultPass', false);
|
||||
TagService.resetTags();
|
||||
$rootScope.$broadcast('logout');
|
||||
$location.path('/');
|
||||
};
|
||||
|
|
|
@ -254,6 +254,8 @@
|
|||
CredentialService.updateCredential(c, true).then(function () {
|
||||
NotificationService.showNotification($translate.instant('credential.unshared'), 4000);
|
||||
$scope.sharing_complete = true;
|
||||
$scope.storedCredential.shared_key = null;
|
||||
$scope.share_settings.credentialSharedWithUserAndGroup = [];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -365,6 +367,7 @@
|
|||
_credential.skip_revision = true;
|
||||
_credential.shared_key = EncryptService.encryptString(key);
|
||||
CredentialService.updateCredential(_credential, true).then(function () {
|
||||
$scope.storedCredential.shared_key = _credential.shared_key;
|
||||
NotificationService.showNotification($translate.instant('credential.shared'), 4000);
|
||||
$scope.sharing_complete = true;
|
||||
});
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
* Controller of the passmanApp
|
||||
*/
|
||||
angular.module('passmanApp')
|
||||
.controller('VaultCtrl', ['$scope', 'VaultService', 'SettingsService', 'CredentialService', '$location', 'ShareService', 'EncryptService', '$translate', '$rootScope', function ($scope, VaultService, SettingsService, CredentialService, $location, ShareService, EncryptService, $translate, $rootScope) {
|
||||
.controller('VaultCtrl', ['$scope', 'VaultService', 'SettingsService', 'CredentialService', '$location', 'ShareService', 'EncryptService', '$translate', '$rootScope', '$interval',
|
||||
function ($scope, VaultService, SettingsService, CredentialService, $location, ShareService, EncryptService, $translate, $rootScope, $interval) {
|
||||
VaultService.getVaults().then(function (vaults) {
|
||||
$scope.vaults = vaults;
|
||||
if (SettingsService.getSetting('defaultVault') != null) {
|
||||
|
@ -50,6 +51,10 @@
|
|||
if (SettingsService.getSetting('defaultVaultPass')) {
|
||||
$location.path('/vault/' + vault.guid);
|
||||
}
|
||||
$scope.vault_tries[vault.guid] = {
|
||||
tries: 0,
|
||||
timeout: 0
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -74,12 +79,12 @@
|
|||
|
||||
var settingsLoaded = function () {
|
||||
$scope.minimal_value_key_strength = SettingsService.getSetting('vault_key_strength');
|
||||
$translate(key_strengths[SettingsService.getSetting('vault_key_strength')]).then(function(translation){
|
||||
$translate(key_strengths[SettingsService.getSetting('vault_key_strength')]).then(function (translation) {
|
||||
$scope.required_score = {'strength': translation};
|
||||
});
|
||||
};
|
||||
|
||||
if(!SettingsService.getSetting('settings_loaded')){
|
||||
if (!SettingsService.getSetting('settings_loaded')) {
|
||||
$rootScope.$on('settings_loaded', function () {
|
||||
settingsLoaded();
|
||||
});
|
||||
|
@ -107,7 +112,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
$scope.toggleAutoLogout = function(){
|
||||
$scope.toggleAutoLogout = function () {
|
||||
$scope.auto_logout_timer = !$scope.auto_logout_timer;
|
||||
};
|
||||
|
||||
|
@ -119,6 +124,12 @@
|
|||
|
||||
$scope.selectVault = function (vault) {
|
||||
$scope.list_selected_vault = vault;
|
||||
if(!$scope.vault_tries[vault.guid]) {
|
||||
$scope.vault_tries[vault.guid] = {
|
||||
tries: 0,
|
||||
timeout: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
$scope.sharing_keys = {};
|
||||
$scope.newVault = function () {
|
||||
|
@ -144,9 +155,9 @@
|
|||
var _vault = angular.copy(vault);
|
||||
_vault.vaultKey = angular.copy(vault_key);
|
||||
delete _vault.credentials;
|
||||
var timer = parseInt($scope.logout_timer);
|
||||
if($scope.auto_logout_timer && timer > 0 ){
|
||||
$rootScope.$broadcast('logout_timer_set', timer*60);
|
||||
var timer = parseInt($scope.logout_timer);
|
||||
if ($scope.auto_logout_timer && timer > 0) {
|
||||
$rootScope.$broadcast('logout_timer_set', timer * 60);
|
||||
}
|
||||
|
||||
VaultService.setActiveVault(_vault);
|
||||
|
@ -158,6 +169,16 @@
|
|||
$scope.logout_timer = time;
|
||||
};
|
||||
|
||||
var tickLockTimer = function (guid) {
|
||||
$scope.vault_tries[guid].timeout = $scope.vault_tries[guid].timeout - 1;
|
||||
if($scope.vault_tries[guid].timeout <= 0){
|
||||
$interval.cancel($scope.vault_tries[guid].timer);
|
||||
$scope.vault_tries[guid].timeout = 0;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.vault_tries = {};
|
||||
|
||||
$scope.vaultDecryptionKey = '';
|
||||
$scope.loginToVault = function (vault, vault_key) {
|
||||
$scope.error = false;
|
||||
|
@ -174,8 +195,23 @@
|
|||
|
||||
} catch (e) {
|
||||
$scope.error = $translate.instant('invalid.vault.key');
|
||||
}
|
||||
|
||||
$scope.vault_tries[vault.guid].tries = $scope.vault_tries[vault.guid].tries + 1;
|
||||
|
||||
if($scope.vault_tries[vault.guid].tries >= 3){
|
||||
var duration = (Math.pow(2, 1 / 7) * Math.pow(15, 4 / 7)) * Math.pow((Math.pow(2, 2 / 7) * Math.pow(15, 1 / 7)), $scope.vault_tries[vault.guid].tries);
|
||||
$scope.vault_tries[vault.guid].timeout = duration;
|
||||
|
||||
if($scope.vault_tries[vault.guid].hasOwnProperty('timer')){
|
||||
$interval.cancel($scope.vault_tries[vault.guid].timer);
|
||||
}
|
||||
|
||||
$scope.vault_tries[vault.guid].timer = $interval(function () {
|
||||
tickLockTimer(vault.guid);
|
||||
} ,1000);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -47,6 +47,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
resetTags: function () {
|
||||
_tags = [];
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -132,3 +132,19 @@ var PassmanImporter = {};
|
|||
return JSON.parse(string);
|
||||
};
|
||||
})(window, $, PassmanImporter);
|
||||
|
||||
|
||||
String.prototype.replaceAll = function(search, replacement) {
|
||||
var target = this;
|
||||
return target.replace(new RegExp(search, 'g'), replacement);
|
||||
};
|
||||
|
||||
Array.prototype.clean = function(deleteValue) {
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (this[i] === deleteValue) {
|
||||
this.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
|
|
@ -29,7 +29,7 @@ var PassmanImporter = PassmanImporter || {};
|
|||
info: {
|
||||
name: 'Clipperz.is',
|
||||
id: 'clippers',
|
||||
description: 'Go to menu -> Export -> Download HTML + JSON. Fields will be imported as custom fields.'
|
||||
exportSteps: ['Go to menu -> Export -> Download HTML + JSON. Fields will be imported as custom fields.']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ var PassmanImporter = PassmanImporter || {};
|
|||
info: {
|
||||
name: 'Dashlane 4 csv',
|
||||
id: 'dashLaneCsv',
|
||||
description: 'Create an csv export. Go to File -> export -> Unsecured archive (readable) in CSV format'
|
||||
exportSteps: ['Create an csv export. Go to File -> export -> Unsecured archive (readable) in CSV format']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
121
js/importers/importer-enpass.js
Normal file
121
js/importers/importer-enpass.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Importers should always start with this
|
||||
/** global: PassmanImporter */
|
||||
var PassmanImporter = PassmanImporter || {};
|
||||
(function(window, $, PassmanImporter) {
|
||||
'use strict';
|
||||
// Define the importer
|
||||
PassmanImporter.EnPassTXT = {
|
||||
info: {
|
||||
name: 'EnPass text file',
|
||||
id: 'EnPassTXT',
|
||||
exportSteps: ['Access your Enpass Database. Select "File" > "Export" > "As Text"']
|
||||
}
|
||||
};
|
||||
|
||||
function parseEnpass(fileData){
|
||||
var lastProperty, matches, loginBlocks, property;
|
||||
loginBlocks = fileData.replaceAll("Title :","<~passman~>\nTitle :").split('<~passman~>\n').clean("");
|
||||
var regex = /(.*) : (.*)/;
|
||||
var results = [];
|
||||
for(var l = 0; l < loginBlocks.length; l++){
|
||||
var loginBlock = loginBlocks[l];
|
||||
var lrow = loginBlock.split('\n');
|
||||
var result = {};
|
||||
for(var r = 0; r < lrow.length; r++){
|
||||
var row = lrow[r];
|
||||
matches = regex.exec(row);
|
||||
if(matches){
|
||||
property = matches[1];
|
||||
result[property] = matches[2];
|
||||
} else {
|
||||
if(lastProperty){
|
||||
result[lastProperty] += "\n" + row;
|
||||
}
|
||||
}
|
||||
if(property) {
|
||||
lastProperty = property;
|
||||
}
|
||||
}
|
||||
results.push(result)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
PassmanImporter.EnPassTXT.readFile = function (file_data) {
|
||||
var mapper = {
|
||||
'Title': 'label',
|
||||
'Username': 'username',
|
||||
'Password': 'password',
|
||||
'Email': 'email',
|
||||
'Url': 'url',
|
||||
'Note': 'description'
|
||||
};
|
||||
|
||||
var secret_fields = ['cvc', 'pin', 'security answer'];
|
||||
|
||||
/** global: C_Promise */
|
||||
return new C_Promise(function(){
|
||||
var credential_list = [];
|
||||
var credentials = parseEnpass(file_data);
|
||||
for (var i = 0; i < credentials.length; i++) {
|
||||
var enpass_credential = credentials[i];
|
||||
var new_credential = PassmanImporter.newCredential();
|
||||
for(var key in enpass_credential){
|
||||
if(!enpass_credential.hasOwnProperty(key)){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(mapper.hasOwnProperty(key)){
|
||||
var prop = mapper[key];
|
||||
new_credential[prop] = enpass_credential[key];
|
||||
} else {
|
||||
if(key !== 'TOTP') {
|
||||
var isSecret = (secret_fields.indexOf(key.toLowerCase()) !== -1) ? 1 : 0;
|
||||
new_credential.custom_fields.push({
|
||||
'label': key,
|
||||
'value': enpass_credential[key],
|
||||
'secret': isSecret
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(enpass_credential.hasOwnProperty('TOTP')){
|
||||
new_credential.otp.secret = enpass_credential['TOTP'];
|
||||
}
|
||||
|
||||
var progress = {
|
||||
percent: i/credentials.length*100,
|
||||
loaded: i,
|
||||
total: credentials.length
|
||||
};
|
||||
|
||||
credential_list.push(new_credential);
|
||||
this.call_progress(progress);
|
||||
}
|
||||
this.call_then(credential_list);
|
||||
});
|
||||
};
|
||||
})(window, $, PassmanImporter);
|
|
@ -30,7 +30,7 @@ var PassmanImporter = PassmanImporter || {};
|
|||
info: {
|
||||
name: 'KeePass csv',
|
||||
id: 'keepassCsv',
|
||||
description: 'Create an csv export with the following options enabled: http://i.imgur.com/CaeTA4d.png'
|
||||
exportSteps: ['Create an csv export with the following options enabled: http://i.imgur.com/CaeTA4d.png']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ var PassmanImporter = PassmanImporter || {};
|
|||
info: {
|
||||
name: 'LastPass csv',
|
||||
id: 'lastpassCsv',
|
||||
description: 'Create an csv export. Go to More options -> Advanced -> Export -> Last Pass CSV File'
|
||||
exportSteps: ['Create an csv export. Go to More options -> Advanced -> Export -> Last Pass CSV File']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
71
js/importers/importer-ocpasswords.js
Normal file
71
js/importers/importer-ocpasswords.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Importers should always start with this
|
||||
/** global: PassmanImporter */
|
||||
var PassmanImporter = PassmanImporter || {};
|
||||
(function (window, $, PassmanImporter) {
|
||||
'use strict';
|
||||
// Define the importer
|
||||
var steps = [
|
||||
'Backups for the Passwords app need to be enabled on the Admin panel (they are disabled by default).',
|
||||
'On the Passwords App, in the bottom left corner, press Settings',
|
||||
'Press "Download Backup"',
|
||||
'Confirm the export and save the file'
|
||||
];
|
||||
PassmanImporter.passwordsApp = {
|
||||
info: {
|
||||
name: 'Passwords App csv',
|
||||
id: 'passwordsApp',
|
||||
exportSteps: steps
|
||||
}
|
||||
};
|
||||
|
||||
PassmanImporter.passwordsApp.readFile = function (file_data) {
|
||||
/** global: C_Promise */
|
||||
var p = new C_Promise(function () {
|
||||
var parsed_csv = PassmanImporter.readCsv(file_data);
|
||||
var credential_list = [];
|
||||
for (var i = 0; i < parsed_csv.length; i++) {
|
||||
var row = parsed_csv[i];
|
||||
var _credential = PassmanImporter.newCredential();
|
||||
_credential.label = row.website + ' - '+ row.username;
|
||||
_credential.username = row.username;
|
||||
_credential.password = row.password;
|
||||
_credential.url = row.fulladdress;
|
||||
_credential.description = row.notes;
|
||||
|
||||
credential_list.push(_credential);
|
||||
|
||||
var progress = {
|
||||
percent: i / parsed_csv.length * 100,
|
||||
loaded: i,
|
||||
total: parsed_csv.length
|
||||
};
|
||||
|
||||
this.call_progress(progress);
|
||||
}
|
||||
this.call_then(credential_list);
|
||||
});
|
||||
return p;
|
||||
};
|
||||
})(window, $, PassmanImporter);
|
|
@ -29,7 +29,7 @@ var PassmanImporter = PassmanImporter || {};
|
|||
info: {
|
||||
name: 'Passman JSON',
|
||||
id: 'passmanJson',
|
||||
description: 'Export the item in passman as passman json, with all fields enabled'
|
||||
exportSteps: ['Export the item in passman as passman json, with all fields enabled']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ var PassmanImporter = PassmanImporter || {};
|
|||
info: {
|
||||
name: 'Passpack csv',
|
||||
id: 'passpackCsv',
|
||||
description: 'Go to Tools -> Export. Select Comma Separated Values, All entries then continue.'
|
||||
exportSteps: ['Go to Tools -> Export. Select Comma Separated Values, All entries then continue.']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ var PassmanImporter = PassmanImporter || {};
|
|||
info: {
|
||||
name: 'Random data',
|
||||
id: 'randomData',
|
||||
description: 'Create\'s 50 random credentials for testing purposes.'
|
||||
exportSteps: ['Create\'s 50 random credentials for testing purposes.']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
62
js/importers/importer-teampass.js
Normal file
62
js/importers/importer-teampass.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Importers should always start with this
|
||||
/** global: PassmanImporter */
|
||||
var PassmanImporter = PassmanImporter || {};
|
||||
(function(window, $, PassmanImporter) {
|
||||
'use strict';
|
||||
// Define the importer
|
||||
PassmanImporter.teamPassCsv = {
|
||||
info: {
|
||||
name: 'TeamPass csv',
|
||||
id: 'teamPassCsv',
|
||||
exportSteps: ['Go to Tools -> Export. Select Comma Separated Values, All entries then continue.']
|
||||
}
|
||||
};
|
||||
|
||||
PassmanImporter.teamPassCsv.readFile = function (file_data) {
|
||||
/** global: C_Promise */
|
||||
return new C_Promise(function(){
|
||||
var parsed_csv = PassmanImporter.readCsv(file_data, false);
|
||||
var credential_list = [];
|
||||
for (var i = 0; i < parsed_csv.length; i++) {
|
||||
var row = parsed_csv[i];
|
||||
var _credential = PassmanImporter.newCredential();
|
||||
_credential.label = row[1];
|
||||
_credential.description = row[2];
|
||||
_credential.password = row[3];
|
||||
_credential.username = row[4];
|
||||
|
||||
var progress = {
|
||||
percent: i/parsed_csv.length*100,
|
||||
loaded: i,
|
||||
total: parsed_csv.length
|
||||
};
|
||||
|
||||
credential_list.push(_credential);
|
||||
this.call_progress(progress);
|
||||
}
|
||||
this.call_then(credential_list);
|
||||
});
|
||||
};
|
||||
})(window, $, PassmanImporter);
|
|
@ -32,7 +32,7 @@ var PassmanImporter = PassmanImporter || {};
|
|||
info: {
|
||||
name: 'ZOHO csv',
|
||||
id: 'zohoCsv',
|
||||
description: 'Create an csv export. Go to Tools -> Export secrets -> Select "General CSV" and click "Export Secrets"'
|
||||
exportSteps: ['Create an csv export. Go to Tools -> Export secrets -> Select "General CSV" and click "Export Secrets"']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -23,7 +23,11 @@
|
|||
|
||||
namespace OCA\Passman;
|
||||
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class Activity implements \OCP\Activity\IExtension {
|
||||
const FILTER_PASSMAN = 'passman';
|
||||
const APP_NAME = 'passman';
|
||||
const TYPE_ITEM_ACTION = 'passman_item_action';
|
||||
const TYPE_ITEM_EXPIRED = 'passman_item_expired';
|
||||
const TYPE_ITEM_SHARED = 'passman_item_shared';
|
||||
|
@ -49,6 +53,13 @@ class Activity implements \OCP\Activity\IExtension {
|
|||
const SUBJECT_ITEM_RENAMED_SELF = 'item_renamed_self';
|
||||
|
||||
|
||||
protected $URLGenerator;
|
||||
|
||||
public function __construct( IURLGenerator $URLGenerator) {
|
||||
$this->URLGenerator = $URLGenerator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The extension can return an array of additional notification types.
|
||||
* If no additional types are to be added false is to be returned
|
||||
|
@ -57,7 +68,7 @@ class Activity implements \OCP\Activity\IExtension {
|
|||
* @return array|false
|
||||
*/
|
||||
public function getNotificationTypes($languageCode) {
|
||||
$l = \OC::$server->getL10N('passman', $languageCode);
|
||||
$l = \OC::$server->getL10N(self::APP_NAME, $languageCode);
|
||||
return array(
|
||||
self::TYPE_ITEM_ACTION => $l->t('A Passman item has been created, modified or deleted'),
|
||||
self::TYPE_ITEM_EXPIRED => $l->t('A Passman item has expired'),
|
||||
|
@ -75,7 +86,7 @@ class Activity implements \OCP\Activity\IExtension {
|
|||
* @return array|false
|
||||
*/
|
||||
public function filterNotificationTypes($types, $filter) {
|
||||
return $types;
|
||||
return $filter === self::FILTER_PASSMAN ? [self::TYPE_ITEM_ACTION, self::TYPE_ITEM_EXPIRED, self::TYPE_ITEM_SHARED, self::TYPE_ITEM_RENAMED] : $types;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,8 +127,8 @@ class Activity implements \OCP\Activity\IExtension {
|
|||
* @return string|false
|
||||
*/
|
||||
public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) {
|
||||
$l = \OC::$server->getL10NFactory()->get('passman', $languageCode);
|
||||
if ($app === 'passman') {
|
||||
$l = \OC::$server->getL10NFactory()->get(self::APP_NAME, $languageCode);
|
||||
if ($app === self::APP_NAME) {
|
||||
switch ($text) {
|
||||
case self::SUBJECT_ITEM_CREATED:
|
||||
return $l->t('%1$s has been created by %2$s', $params);
|
||||
|
@ -172,7 +183,7 @@ class Activity implements \OCP\Activity\IExtension {
|
|||
* @return array|false
|
||||
*/
|
||||
public function getSpecialParameterList($app, $text) {
|
||||
if ($app === 'passman') {
|
||||
if ($app === self::APP_NAME) {
|
||||
switch ($text) {
|
||||
case self::SUBJECT_ITEM_CREATED:
|
||||
case self::SUBJECT_ITEM_CREATED_SELF:
|
||||
|
@ -247,14 +258,14 @@ class Activity implements \OCP\Activity\IExtension {
|
|||
* @return array|false
|
||||
*/
|
||||
public function getNavigation() {
|
||||
$l = \OC::$server->getL10N('passman');
|
||||
$l = \OC::$server->getL10N(self::APP_NAME);
|
||||
return array(
|
||||
'top' => array(),
|
||||
'apps' => array(
|
||||
'apps' => array( self::FILTER_PASSMAN =>
|
||||
array(
|
||||
'id' => 'passman',
|
||||
'name' => (string) $l->t('Passwords'),
|
||||
'url' => '',//FIXME: Currenlty we cannot link directly to links, so this needs to be fixed... this->URLGenerator->linkToRoute('activity.Activities.showList', array('filter' => 'passman')),
|
||||
'url' => $this->URLGenerator->linkToRoute('activity.Activities.showList', ['filter' => self::FILTER_PASSMAN]),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -267,7 +278,7 @@ class Activity implements \OCP\Activity\IExtension {
|
|||
* @return boolean
|
||||
*/
|
||||
public function isFilterValid($filterValue) {
|
||||
return $filterValue === 'passman';
|
||||
return $filterValue === self::FILTER_PASSMAN;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -280,9 +291,13 @@ class Activity implements \OCP\Activity\IExtension {
|
|||
* @return array|false
|
||||
*/
|
||||
public function getQueryForFilter($filter) {
|
||||
if ($filter === 'passman') {
|
||||
return array('`app` = ?', array('passman'));
|
||||
if ($filter === self::FILTER_PASSMAN) {
|
||||
return [
|
||||
'(`app` = ?)',
|
||||
[self::APP_NAME],
|
||||
];
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
|
@ -106,4 +106,15 @@
|
|||
//Fix NC fucking up our style
|
||||
input[type="checkbox"]{
|
||||
min-height: inherit;
|
||||
}
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.alert-danger {
|
||||
color: #a94442;
|
||||
background-color: #f2dede;
|
||||
border-color: #ebccd1;
|
||||
}
|
|
@ -375,6 +375,9 @@
|
|||
.file_tab {
|
||||
|
||||
}
|
||||
.otpText{
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
.app_sidebar {
|
||||
h2{
|
||||
|
|
|
@ -40,4 +40,11 @@
|
|||
}
|
||||
.tab_container.settings{
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
.import-steps{
|
||||
padding-left: 16px;
|
||||
li{
|
||||
list-style-type: disc;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
|
@ -90,6 +90,9 @@
|
|||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
.login_opts{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px){
|
||||
|
|
|
@ -82,7 +82,10 @@ script('passman', 'importers/importer-lastpasscsv');
|
|||
script('passman', 'importers/importer-dashlanecsv');
|
||||
script('passman', 'importers/importer-zohocsv');
|
||||
script('passman', 'importers/importer-passmanjson');
|
||||
script('passman', 'importers/importer-ocpasswords');
|
||||
script('passman', 'importers/importer-clipperz');
|
||||
script('passman', 'importers/importer-teampass');
|
||||
script('passman', 'importers/importer-enpass');
|
||||
script('passman', 'importers/importer-passpackcsv');
|
||||
script('passman', 'importers/importer-randomdata');
|
||||
script('passman', 'exporters/exporter-main');
|
||||
|
@ -96,7 +99,7 @@ script('passman', 'exporters/exporter-csv');
|
|||
/*build-css-start*/
|
||||
style('passman', 'vendor/ng-password-meter/ng-password-meter');
|
||||
style('passman', 'vendor/bootstrap/bootstrap.min');
|
||||
style('passman', 'vendor/bootstrap/bootstrap-theme.min');
|
||||
|
||||
style('passman', 'vendor/font-awesome/font-awesome.min');
|
||||
style('passman', 'vendor/angular-xeditable/xeditable.min');
|
||||
style('passman', 'vendor/ng-tags-input/ng-tags-input.min');
|
||||
|
|
|
@ -9,17 +9,31 @@ $checkVersion = OC::$server->getConfig()->getAppValue('passman', 'check_version'
|
|||
$AppInstance = new App();
|
||||
$localVersion = $AppInstance->getAppInfo("passman")["version"];
|
||||
if ($checkVersion) {
|
||||
// get latest master version
|
||||
$doc = new DOMDocument();
|
||||
$doc->load('https://raw.githubusercontent.com/nextcloud/passman/master/appinfo/info.xml');
|
||||
$root = $doc->getElementsByTagName("info");
|
||||
// get latest master version
|
||||
$version = false;
|
||||
$githubVersion = $l->t('Unable to get version info');
|
||||
foreach ($root as $element) {
|
||||
$versions = $element->getElementsByTagName("version");
|
||||
$version = $versions->item(0)->nodeValue;
|
||||
|
||||
$url = 'https://raw.githubusercontent.com/nextcloud/passman/master/appinfo/info.xml';
|
||||
try {
|
||||
$client = OC::$server->getHTTPClientService()->newClient();
|
||||
$response = $client->get($url);
|
||||
$xml = $response->getBody();
|
||||
} catch (\Exception $e) {
|
||||
$xml = false;
|
||||
}
|
||||
if ($version) {
|
||||
|
||||
if ($xml) {
|
||||
$loadEntities = libxml_disable_entity_loader(true);
|
||||
$data = @simplexml_load_string($xml);
|
||||
libxml_disable_entity_loader($loadEntities);
|
||||
if ($data !== false) {
|
||||
$version = (string)$data->version;
|
||||
} else {
|
||||
libxml_clear_errors();
|
||||
}
|
||||
}
|
||||
|
||||
if ($version !== false) {
|
||||
$githubVersion = $version;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +50,7 @@ $ciphers = openssl_get_cipher_methods();
|
|||
} ?>
|
||||
Local version: <?php p($localVersion); ?><br/>
|
||||
<?php
|
||||
if (version_compare($githubVersion, $localVersion) === 1) {
|
||||
if ($checkVersion && version_compare($githubVersion, $localVersion) === 1) {
|
||||
p($l->t('A newer version of passman is available'));
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="col-xs-2 nopadding">
|
||||
{{ 'upload.qr' | translate}}
|
||||
<div class="col-xs-4 nopadding">
|
||||
<span class="otpText">{{ 'upload.qr' | translate}}</span>
|
||||
<select ng-model="otpType">
|
||||
<option value="qrcode">Upload QR code</option>
|
||||
<option value="secret">Enter secret</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-6 nopadding">
|
||||
<input type="file" qrread on-read="parseQR(qrdata)"
|
||||
class="input_secret"
|
||||
on-read="parseQR(qrdata)"/>
|
||||
on-read="parseQR(qrdata)" ng-show="otpType === 'qrcode'"/>
|
||||
<input type="text" ng-model="storedCredential.otp.secret" ng-show="otpType === 'secret'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12" ng-if="storedCredential.otp">{{ 'current.qr' | translate}}
|
||||
<div class="col-xs-12" ng-if="storedCredential.otp">
|
||||
<b>{{ 'current.qr' | translate}}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@ -22,23 +28,23 @@
|
|||
<div class="col-sm-4 col-sm-5 col-md-5">
|
||||
<table ng-show="storedCredential.otp">
|
||||
<tr ng-show="storedCredential.otp.type">
|
||||
<td>{{ 'type' | translate}}:</td>
|
||||
<td>{{ 'type' | translate}}: </td>
|
||||
<td>{{storedCredential.otp.type}}</td>
|
||||
</tr>
|
||||
<tr ng-show="storedCredential.otp.label">
|
||||
<td>{{ 'label' | translate}}:</td>
|
||||
<td>{{ 'label' | translate}}: </td>
|
||||
<td>{{storedCredential.otp.label}}</td>
|
||||
</tr>
|
||||
<tr ng-show="storedCredential.otp.issuer">
|
||||
<td>{{ 'issuer' | translate}}:</td>
|
||||
<td>{{ 'issuer' | translate}}: </td>
|
||||
<td>{{storedCredential.otp.issuer}}</td>
|
||||
</tr>
|
||||
<tr ng-show="storedCredential.otp.secret">
|
||||
<td>{{ 'secret' | translate}}:</td>
|
||||
<td>{{ 'secret' | translate}}: </td>
|
||||
<td>{{storedCredential.otp.secret}}</td>
|
||||
</tr>
|
||||
<tr ng-show="storedCredential.otp.secret">
|
||||
<td>{{ 'otp' | translate}}:</td>
|
||||
<td>{{ 'otp' | translate}}: </td>
|
||||
<td><span otp-generator
|
||||
secret="storedCredential.otp.secret"></span>
|
||||
</td>
|
||||
|
|
|
@ -10,13 +10,26 @@
|
|||
</option>
|
||||
</select></label>
|
||||
<div><b>{{selectedExporter.description}}</b></div>
|
||||
<button class="button" ng-click="startExport()"
|
||||
ng-if="selectedExporter">{{ 'export' | translate}}
|
||||
</button>
|
||||
|
||||
<div ng-show="selectedExporter" class="col-xs-3 nopadding">
|
||||
<label>{{ 'export.confirm.text' | translate }}</label>
|
||||
<input type="password" ng-model="confirm_key">
|
||||
<br />
|
||||
<div class="alert alert-warning" ng-show="error">
|
||||
{{error}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<button class=" button" ng-click="startExport()"
|
||||
ng-if="selectedExporter">{{ 'export' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-6">
|
||||
<div ng-if="log" class="import_log">
|
||||
<textarea id="import_log" auto-scroll="log">{{log.join('\n')}}</textarea>
|
||||
<textarea id="import_log"
|
||||
auto-scroll="log">{{log.join('\n')}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,12 @@
|
|||
{{importer.name}}
|
||||
</option>
|
||||
</select></label>
|
||||
<div><b>{{selectedImporter.description}}</b></div>
|
||||
<div ng-show="selectedImporter">
|
||||
<b>{{ 'import.steps' | translate }}</b>
|
||||
<ul class="import-steps">
|
||||
<li ng-repeat="step in selectedImporter.exportSteps">{{step}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input ng-if="selectedImporter" type="file" file-select
|
||||
success="fileLoaded" error="fileLoadError"
|
||||
progress="fileSelectProgress"><br/>
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<small>{{ 'search.result.missing' | translate}}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
<div class="tab_container share_credential" ng-show="currentTab">
|
||||
<div ng-include="currentTab.url"></div>
|
||||
|
||||
<button ng-click="applyShare()" ng-disabled="!share_settings.linkSharing.enabled || share_settings.credentialSharedWithUserAndGroup.length > 0">{{ 'share' | translate}}</button>
|
||||
<button ng-click="applyShare()" ng-disabled="share_settings.linkSharing.enabled === false && share_settings.credentialSharedWithUserAndGroup.length === 0">{{ 'share' | translate}}</button>
|
||||
<button ng-click="cancel()">{{ 'cancel' | translate}}</button>
|
||||
<button class="btn btn-danger" ng-click="unshareCredential(storedCredential)">{{ 'unshare' | translate}}</button>
|
||||
<button class="btn btn-danger" ng-disabled="!storedCredential.shared_key" ng-click="unshareCredential(storedCredential)">{{ 'unshare' | translate}}</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<a><i class="fa fa-home"></i></a>
|
||||
</div>
|
||||
<div class="crumb svg last" ng-click="clearState()">
|
||||
<a>{{active_vault.name}}</a>
|
||||
<span>{{active_vault.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
<span ng-if="list_selected_vault.last_access === 0">Never</span>
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<div class="login_opts">
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" ng-checked="default_vault"
|
||||
|
@ -128,10 +128,12 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button button-geen"
|
||||
ng-click="loginToVault(list_selected_vault, vault_key)">
|
||||
|
||||
<div class="alert alert-danger" ng-show="vault_tries[list_selected_vault.guid].timeout !== 0" translate="vault.locked" translate-value-tries="{{ vault_tries[list_selected_vault.guid].tries }}" translate-value-time="{{ vault_tries[list_selected_vault.guid].timeout | toHHMMSS }}"></div>
|
||||
<button class="button button-geen"
|
||||
ng-click="loginToVault(list_selected_vault, vault_key)" ng-disabled="vault_tries[list_selected_vault.guid].timeout !== 0">
|
||||
{{ 'vault.decrypt' | translate}}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue