Expire passwords working, requires notifications app

This commit is contained in:
brantje 2016-09-23 15:05:27 +02:00
parent 35262e737f
commit 6eafc2d067
14 changed files with 459 additions and 193 deletions

View file

@ -13,14 +13,34 @@ namespace OCA\Passman\AppInfo;
use OCP\Util;
use OCP\BackgroundJob;
use OCA\Passman\Notifier;
require_once __DIR__ . '/autoload.php';
$app = new \OCA\Passman\AppInfo\Application();
$app->registerNavigationEntry();
$app->registerPersonalPage();
$l = \OC::$server->getL10N('passman');
$manager = \OC::$server->getNotificationManager();
$manager->registerNotifier(function() {
return new Notifier(
\OC::$server->getL10NFactory()
);
}, function() use ($l) {
return [
'id' => 'passman',
'name' => $l->t('Passwords'),
];
});
/**
* Loading translations
*
* The string has to match the app's folder name
*/
Util::addTranslations('passman');
Util::addTranslations('passman');
\OCP\BackgroundJob::addRegularTask('\OCA\Passman\Cron\ExpireCredentials', 'run');

View file

@ -22,7 +22,8 @@ angular
'ngPasswordMeter',
'ngclipboard',
'xeditable',
'ngTagsInput'
'ngTagsInput',
'angularjs-datetime-picker'
])
.config(function ($routeProvider) {
$routeProvider

View file

@ -10,198 +10,208 @@
angular.module('passmanApp')
.controller('CredentialEditCtrl', ['$scope', 'VaultService', 'CredentialService', 'SettingsService', '$location', '$routeParams', 'FileService', 'EncryptService', 'TagService', 'NotificationService',
function ($scope, VaultService, CredentialService, SettingsService, $location, $routeParams, FileService, EncryptService, TagService, NotificationService) {
$scope.active_vault = VaultService.getActiveVault();
$scope.active_vault = VaultService.getActiveVault();
$scope.tabs = [{
title: 'General',
url: 'views/partials/forms/edit_credential/basics.html',
color: 'blue'
}, {
title: 'Password',
url: 'views/partials/forms/edit_credential/password.html',
color: 'green'
}, {
title: 'Custom fields',
url: 'views/partials/forms/edit_credential/custom_fields.html',
color: 'orange'
}, {
title: 'Files',
url: 'views/partials/forms/edit_credential/files.html',
color: 'yellow'
}, {
title: 'OTP',
url: 'views/partials/forms/edit_credential/otp.html',
color: 'purple'
}];
$scope.tabs = [{
title: 'General',
url: 'views/partials/forms/edit_credential/basics.html',
color: 'blue'
}, {
title: 'Password',
url: 'views/partials/forms/edit_credential/password.html',
color: 'green'
}, {
title: 'Custom fields',
url: 'views/partials/forms/edit_credential/custom_fields.html',
color: 'orange'
}, {
title: 'Files',
url: 'views/partials/forms/edit_credential/files.html',
color: 'yellow'
}, {
title: 'OTP',
url: 'views/partials/forms/edit_credential/otp.html',
color: 'purple'
}];
$scope.pwSettings = {
'length': 12,
'useUppercase': true,
'useLowercase': true,
'useDigits': true,
'useSpecialChars': true,
'minimumDigitCount': 3,
'avoidAmbiguousCharacters': false,
'requireEveryCharType': true
};
if (!SettingsService.getSetting('defaultVault') || !SettingsService.getSetting('defaultVaultPass')) {
if (!$scope.active_vault) {
$location.path('/')
}
} else {
if (SettingsService.getSetting('defaultVault') && SettingsService.getSetting('defaultVaultPass')) {
var _vault = angular.copy(SettingsService.getSetting('defaultVault'))
_vault.vaultKey = angular.copy(SettingsService.getSetting('defaultVaultPass'));
VaultService.setActiveVault(_vault);
$scope.active_vault = _vault;
}
}
if ($scope.active_vault) {
$scope.$parent.selectedVault = true;
}
var storedCredential = SettingsService.getSetting('edit_credential');
if (!storedCredential) {
$location.path('/vault/' + $routeParams.vault_id);
} else {
$scope.storedCredential = CredentialService.decryptCredential(angular.copy(storedCredential));
$scope.storedCredential.password_repeat = angular.copy($scope.storedCredential.password);
}
$scope.getTags = function ($query) {
return TagService.searchTag($query);
};
$scope.currentTab = {
title: 'General',
url: 'views/partials/forms/edit_credential/basics.html',
color: 'blue'
};
$scope.onClickTab = function (tab) {
$scope.currentTab = tab;
};
$scope.isActiveTab = function (tab) {
return tab.url == $scope.currentTab.url;
};
/**
* Below general edit functions
*/
$scope.pwGenerated = function (pass) {
$scope.storedCredential.password_repeat = pass;
};
var _customField = {
label: '',
value: '',
secret: false
};
$scope.new_custom_field = angular.copy(_customField);
$scope.addCustomField = function () {
if (!$scope.new_custom_field.label) {
NotificationService.showNotification('Please fill in a label', 3000);
}
if (!$scope.new_custom_field.value) {
NotificationService.showNotification('Please fill in a value!', 3000);
}
if (!$scope.new_custom_field.label || !$scope.new_custom_field.value) {
return;
}
$scope.storedCredential.custom_fields.push(angular.copy($scope.new_custom_field));
$scope.new_custom_field = angular.copy(_customField);
};
$scope.deleteCustomField = function(field){
var idx = $scope.storedCredential.custom_fields.indexOf(field);
$scope.storedCredential.custom_fields.splice(idx, 1);
};
$scope.new_file = {
name: '',
data: null
};
$scope.deleteFile = function(file){
var idx = $scope.storedCredential.files.indexOf(file);
FileService.deleteFile(file).then(function () {
$scope.storedCredential.files.splice(idx, 1);
});
};
$scope.fileLoaded = function (file) {
var _file = {
filename: file.name,
size: file.size,
mimetype: file.type,
data: file.data
$scope.pwSettings = {
'length': 12,
'useUppercase': true,
'useLowercase': true,
'useDigits': true,
'useSpecialChars': true,
'minimumDigitCount': 3,
'avoidAmbiguousCharacters': false,
'requireEveryCharType': true
};
FileService.uploadFile(_file).then(function (result) {
delete result.file_data;
result.filename = EncryptService.decryptString(result.filename);
$scope.storedCredential.files.push(result);
});
$scope.$apply()
};
$scope.fileLoadError = function (error, file) {
console.log(error, file)
};
$scope.selected_file = '';
$scope.fileprogress = [];
$scope.fileSelectProgress = function (progress) {
if (progress) {
$scope.fileprogress = progress;
$scope.$apply()
}
};
$scope.parseQR = function(QRCode){
var re = /otpauth:\/\/(totp|hotp)\/(.*)\?(secret|issuer)=(.*)&(issuer|secret)=(.*)/, parsedQR,qrInfo;
parsedQR = (QRCode.qrData.match(re));
if(parsedQR)
qrInfo = {
type: parsedQR[1],
label: decodeURIComponent(parsedQR[2]),
qr_uri: QRCode
};
qrInfo[parsedQR[3]] = parsedQR[4];
qrInfo[parsedQR[5]] = parsedQR[6];
$scope.storedCredential.otp = qrInfo;
$scope.$apply()
};
$scope.saveCredential = function () {
//@TODO validation
delete $scope.storedCredential.password_repeat;
if(!$scope.storedCredential.credential_id){
$scope.storedCredential.vault_id = $scope.active_vault.vault_id;
CredentialService.createCredential($scope.storedCredential).then(function (result) {
$location.path('/vault/' + $routeParams.vault_id);
NotificationService.showNotification('Credential created!', 5000)
})
if (!SettingsService.getSetting('defaultVault') || !SettingsService.getSetting('defaultVaultPass')) {
if (!$scope.active_vault) {
$location.path('/')
}
} else {
CredentialService.updateCredential($scope.storedCredential).then(function (result) {
SettingsService.setSetting('edit_credential', null);
$location.path('/vault/' + $routeParams.vault_id);
NotificationService.showNotification('Credential updated!', 5000)
})
}
};
if (SettingsService.getSetting('defaultVault') && SettingsService.getSetting('defaultVaultPass')) {
var _vault = angular.copy(SettingsService.getSetting('defaultVault'))
_vault.vaultKey = angular.copy(SettingsService.getSetting('defaultVaultPass'));
VaultService.setActiveVault(_vault);
$scope.active_vault = _vault;
$scope.cancel = function(){
$location.path('/vault/' + $routeParams.vault_id);
}
}]);
}
}
if ($scope.active_vault) {
$scope.$parent.selectedVault = true;
}
var storedCredential = SettingsService.getSetting('edit_credential');
if (!storedCredential) {
$location.path('/vault/' + $routeParams.vault_id);
} else {
$scope.storedCredential = CredentialService.decryptCredential(angular.copy(storedCredential));
$scope.storedCredential.password_repeat = angular.copy($scope.storedCredential.password);
$scope.storedCredential.expire_time = $scope.storedCredential.expire_time * 1000;
}
$scope.getTags = function ($query) {
return TagService.searchTag($query);
};
$scope.currentTab = {
title: 'General',
url: 'views/partials/forms/edit_credential/basics.html',
color: 'blue'
};
$scope.onClickTab = function (tab) {
$scope.currentTab = tab;
};
$scope.isActiveTab = function (tab) {
return tab.url == $scope.currentTab.url;
};
/**
* Below general edit functions
*/
$scope.pwGenerated = function (pass) {
$scope.storedCredential.password_repeat = pass;
};
var _customField = {
label: '',
value: '',
secret: false
};
$scope.new_custom_field = angular.copy(_customField);
$scope.addCustomField = function () {
if (!$scope.new_custom_field.label) {
NotificationService.showNotification('Please fill in a label', 3000);
}
if (!$scope.new_custom_field.value) {
NotificationService.showNotification('Please fill in a value!', 3000);
}
if (!$scope.new_custom_field.label || !$scope.new_custom_field.value) {
return;
}
$scope.storedCredential.custom_fields.push(angular.copy($scope.new_custom_field));
$scope.new_custom_field = angular.copy(_customField);
};
$scope.deleteCustomField = function (field) {
var idx = $scope.storedCredential.custom_fields.indexOf(field);
$scope.storedCredential.custom_fields.splice(idx, 1);
};
$scope.new_file = {
name: '',
data: null
};
$scope.deleteFile = function (file) {
var idx = $scope.storedCredential.files.indexOf(file);
FileService.deleteFile(file).then(function () {
$scope.storedCredential.files.splice(idx, 1);
});
};
$scope.fileLoaded = function (file) {
var _file = {
filename: file.name,
size: file.size,
mimetype: file.type,
data: file.data
};
FileService.uploadFile(_file).then(function (result) {
delete result.file_data;
result.filename = EncryptService.decryptString(result.filename);
$scope.storedCredential.files.push(result);
});
$scope.$apply()
};
$scope.fileLoadError = function (error, file) {
console.log(error, file)
};
$scope.selected_file = '';
$scope.fileprogress = [];
$scope.fileSelectProgress = function (progress) {
if (progress) {
$scope.fileprogress = progress;
$scope.$apply()
}
};
$scope.renewIntervalValue = 0;
$scope.renewIntervalModifier = '0';
$scope.updateInterval = function(renewIntervalValue, renewIntervalModifier){
var value = parseInt(renewIntervalValue);
var modifier = parseInt(renewIntervalModifier);
if( value && modifier) {
$scope.storedCredential.renew_interval = value * modifier;
}
};
$scope.parseQR = function (QRCode) {
var re = /otpauth:\/\/(totp|hotp)\/(.*)\?(secret|issuer)=(.*)&(issuer|secret)=(.*)/, parsedQR, qrInfo;
parsedQR = (QRCode.qrData.match(re));
if (parsedQR)
qrInfo = {
type: parsedQR[1],
label: decodeURIComponent(parsedQR[2]),
qr_uri: QRCode
};
qrInfo[parsedQR[3]] = parsedQR[4];
qrInfo[parsedQR[5]] = parsedQR[6];
$scope.storedCredential.otp = qrInfo;
$scope.$apply()
};
$scope.saveCredential = function () {
//@TODO validation
delete $scope.storedCredential.password_repeat;
if (!$scope.storedCredential.credential_id) {
$scope.storedCredential.vault_id = $scope.active_vault.vault_id;
CredentialService.createCredential($scope.storedCredential).then(function (result) {
$location.path('/vault/' + $routeParams.vault_id);
NotificationService.showNotification('Credential created!', 5000)
})
} else {
CredentialService.updateCredential($scope.storedCredential).then(function (result) {
SettingsService.setSetting('edit_credential', null);
$location.path('/vault/' + $routeParams.vault_id);
NotificationService.showNotification('Credential updated!', 5000)
})
}
};
$scope.cancel = function () {
$location.path('/vault/' + $routeParams.vault_id);
}
}]);

View file

@ -46,6 +46,8 @@ angular.module('passmanApp')
_credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue));
}
_credential.expire_time = new Date( angular.copy(credential.expire_time) ).getTime() / 1000;
var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials');
return $http.post(queryUrl, _credential).then(function (response) {
if (response.data) {
@ -62,6 +64,7 @@ angular.module('passmanApp')
var fieldValue = angular.copy(credential[field]);
_credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue));
}
_credential.expire_time = new Date( angular.copy(credential.expire_time) ).getTime() / 1000;
var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials/' + credential.credential_id);
return $http.patch(queryUrl, _credential).then(function (response) {

View file

@ -33,7 +33,7 @@ angular.module('views/partials/forms/edit_credential/otp.html', []).run(['$templ
angular.module('views/partials/forms/edit_credential/password.html', []).run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('views/partials/forms/edit_credential/password.html',
'<div class="row"><div class="col-xs-12 col-md-5 col-lg-5"><label>Password</label><div><password-gen ng-model="storedCredential.password" settings="pwSettings" callback="pwGenerated"></password-gen><ng-password-meter password="storedCredential.password"></ng-password-meter></div><label>Repeat password</label><div><input type="password" ng-model="storedCredential.password_repeat"></div><label>Expire date</label><div></div><label>Renew interval</label><div></div></div><div class="col-xs-12 col-md-7 col-lg-7">Password generation settings<div class="row"><div class="password_settings"><div class="col-xs-12 col-sm-5 col-lg-4"><label><span class="label">Password length</span><br><input type="number" ng-model="pwSettings.length" min="1"></label><label><span class="label">Minimum amount of digits</span><br><input type="number" ng-model="pwSettings.minimumDigitCount" min="0"></label></div><div class="col-xs-12 col-sm-6 col-lg-6"><label><input type="checkbox" ng-model="pwSettings.useUppercase"> <span class="label sm">Use uppercase letters</span></label><label><input ng-model="pwSettings.useLowercase" type="checkbox" id="lower"> <span class="label sm">Use lowercase letters</span></label><label><input ng-model="pwSettings.useDigits" type="checkbox" id="digits"> <span class="label sm">Use numbers</span></label><label><input type="checkbox" id="special" ng-model="pwSettings.useSpecialChars"> <span class="label sm">Use special characters</span></label><label><input type="checkbox" id="ambig" ng-model="pwSettings.avoidAmbiguousCharacters"> <span class="label sm">Avoid ambiguous characters</span></label><label><input type="checkbox" ng-model="pwSettings.requireEveryCharType" id="reqevery"> <span class="label sm">Require every character type</span></label></div></div></div></div></div>');
'<div class="row"><div class="col-xs-12 col-md-5 col-lg-5"><label>Password</label><div><password-gen ng-model="storedCredential.password" settings="pwSettings" callback="pwGenerated"></password-gen><ng-password-meter password="storedCredential.password"></ng-password-meter></div><label>Repeat password</label><div><input type="password" ng-model="storedCredential.password_repeat"></div><label>Expire date</label><div><span datetime-picker ng-model="storedCredential.expire_time" class="link" future-only ng-show="storedCredential.expire_time == 0" close-on-select="false">No expire date set</span> <span datetime-picker ng-model="storedCredential.expire_time" class="link" future-only ng-show="storedCredential.expire_time != 0" close-on-select="false">{{ storedCredential.expire_time | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span></div><label>Renew interval</label><div><input type="number" ng-model="renewIntervalValue" min="0" ng-change="updateInterval(renewIntervalValue, renewIntervalModifier)"><select ng-model="renewIntervalModifier" ng-change="updateInterval(renewIntervalValue, renewIntervalModifier)"><option value="0">Disabled</option><option value="86400">Day(s)</option><option value="604800">Week(s)</option><option value="2592000">Month(s)</option><option value="31622400">Year(s)</option></select></div></div><div class="col-xs-12 col-md-7 col-lg-7">Password generation settings<div class="row"><div class="password_settings"><div class="col-xs-12 col-sm-5 col-lg-4"><label><span class="label">Password length</span><br><input type="number" ng-model="pwSettings.length" min="1"></label><label><span class="label">Minimum amount of digits</span><br><input type="number" ng-model="pwSettings.minimumDigitCount" min="0"></label></div><div class="col-xs-12 col-sm-6 col-lg-6"><label><input type="checkbox" ng-model="pwSettings.useUppercase"> <span class="label sm">Use uppercase letters</span></label><label><input ng-model="pwSettings.useLowercase" type="checkbox" id="lower"> <span class="label sm">Use lowercase letters</span></label><label><input ng-model="pwSettings.useDigits" type="checkbox" id="digits"> <span class="label sm">Use numbers</span></label><label><input type="checkbox" id="special" ng-model="pwSettings.useSpecialChars"> <span class="label sm">Use special characters</span></label><label><input type="checkbox" id="ambig" ng-model="pwSettings.avoidAmbiguousCharacters"> <span class="label sm">Avoid ambiguous characters</span></label><label><input type="checkbox" ng-model="pwSettings.requireEveryCharType" id="reqevery"> <span class="label sm">Require every character type</span></label></div></div></div></div></div>');
}]);
angular.module('views/partials/forms/share_credential/basics.html', []).run(['$templateCache', function($templateCache) {

View file

@ -16,7 +16,10 @@ use OCA\Passman\Controller\CredentialController;
use OCA\Passman\Controller\PageController;
use OCA\Passman\Controller\ShareController;
use OCA\Passman\Controller\VaultController;
use OCA\Passman\Service\UserService;
use OCA\Passman\Service\CronService;
use OCA\Passman\Service\CredentialService;
use OCA\Passman\Utility\Utils;
use OCA\Passman\Service\NotificationService;
use OCP\AppFramework\App;
use OCP\IL10N;
@ -52,10 +55,30 @@ class Application extends App {
);
});
/** Cron **/
$container->registerService('CronService', function ($c) {
return new CronService(
$c->query('CredentialService'),
$c->query('Logger'),
$c->query('Utils'),
$c->query('NotificationService')
);
});
$container->registerService('Logger', function($c) {
return $c->query('ServerContainer')->getLogger();
});
// Aliases for the controllers so we can use the automatic DI
$container->registerAlias('CredentialController', CredentialController::class);
$container->registerAlias('PageController', PageController::class);
$container->registerAlias('VaultController', VaultController::class);
$container->registerAlias('CredentialService', CredentialService::class);
$container->registerAlias('NotificationService', NotificationService::class);
$container->registerAlias('Utils', Utils::class);
}
/**

View file

@ -0,0 +1,23 @@
<?php
/**
* Nextcloud - passman
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Sander Brand <brantje@gmail.com>
* @copyright Sander Brand 2016
*/
namespace OCA\Passman\Cron;
use OCA\Passman\Service\CredentialService;
use OCA\Passman\Utility\Utils;
use \OCA\Passman\AppInfo\Application;
class ExpireCredentials {
public static function run() {
$app = new Application();
$container = $app->getContainer();
$container->query('CronService')->expireCredentials();
}
}

View file

@ -32,6 +32,12 @@ class CredentialMapper extends Mapper {
return $this->findEntities($sql, [$user_id, $vault_id]);
}
public function getExpiredCredentials($timestamp){
$sql = 'SELECT * FROM `*PREFIX*passman_credentials` ' .
'WHERE `expire_time` > 0 AND `expire_time` < ?';
return $this->findEntities($sql, [$timestamp]);
}
public function create($raw_credential){
$credential = new Credential();

69
lib/Notifier.php Normal file
View file

@ -0,0 +1,69 @@
<?php
/**
* Nextcloud - passman
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Sander Brand <brantje@gmail.com>
* @copyright Sander Brand 2016
*/
namespace OCA\Passman;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
class Notifier implements INotifier {
protected $factory;
public function __construct(\OCP\L10N\IFactory $factory) {
$this->factory = $factory;
}
/**
* @param INotification $notification
* @param string $languageCode The code of the language that should be used to prepare the notification
*/
public function prepare(INotification $notification, $languageCode) {
if ($notification->getApp() !== 'passman') {
// Not my app => throw
throw new \InvalidArgumentException();
}
// Read the language from the notification
$l = $this->factory->get('passman', $languageCode);
switch ($notification->getSubject()) {
// Deal with known subjects
case 'credential_expired':
$notification->setParsedSubject(
(string) $l->t('Your credential "%s" expired', $notification->getSubjectParameters())
);
// Deal with the actions for a known subject
foreach ($notification->getActions() as $action) {
switch ($action->getLabel()) {
case 'change':
$action->setParsedLabel(
(string) $l->t('Change')
);
break;
case 'ignore':
$action->setParsedLabel(
(string) $l->t('Ignore')
);
break;
}
$notification->addParsedAction($action);
}
return $notification;
break;
default:
// Unknown subject => Unknown notification => throw
throw new \InvalidArgumentException();
}
}
}

View file

@ -36,4 +36,7 @@ class CredentialService {
public function getCredentialsByVaultId($vault_id, $user_id){
return $this->credentialMapper->getCredentialsByVaultId($vault_id, $user_id);
}
public function getExpiredCredentials($timestamp){
return $this->credentialMapper->getExpiredCredentials($timestamp);
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* Nextcloud - passman
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Sander Brand <brantje@gmail.com>
* @copyright Sander Brand 2016
*/
namespace OCA\Passman\Service;
use OCP\IConfig;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\ILogger;
use OCA\Passman\Utility\Utils;
class CronService {
private $credentialService;
private $logger;
private $utils;
private $notificationService;
public function __construct(CredentialService $credentialService, ILogger $logger, Utils $utils, NotificationService $notificationService) {
$this->credentialService = $credentialService;
$this->logger = $logger;
$this->utils = $utils;
$this->notificationService = $notificationService;
}
public function expireCredentials() {
$this->logger->info('Passman cron test', array('app' => 'passman'));
$expired_credentials = $this->credentialService->getExpiredCredentials($this->utils->getTime());
foreach($expired_credentials as $credential){
$this->notificationService->credentialExpiredNotification($credential);
}
}
}

View file

@ -0,0 +1,50 @@
<?php
/**
* Nextcloud - passman
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Sander Brand <brantje@gmail.com>
* @copyright Sander Brand 2016
*/
namespace OCA\Passman\Service;
use OCP\IConfig;
use OCP\AppFramework\Db\DoesNotExistException;
use OCA\Passman\Db\FileMapper;
class NotificationService {
private $manager;
public function __construct(FileMapper $fileMapper) {
$this->manager = \OC::$server->getNotificationManager();
}
function credentialExpiredNotification($credential){
$notification = $this->manager->createNotification();
$acceptAction = $notification->createAction();
$acceptAction->setLabel('change')
->setLink('/apps/passman/api/v1/', 'POST');
$declineAction = $notification->createAction();
$declineAction->setLabel('ignore')
->setLink('/apps/passman/internal/notifications/read', 'DELETE');
$notification->setApp('passman')
->setUser($credential->getUserId())
->setDateTime(new \DateTime())
->setObject('credential', $credential->getId()) // $type and $id
->setSubject('credential_expired', [$credential->getLabel()]) // $subject and $parameters
->setLink('/apps/passman/#/vault/'. $credential->getVaultId() .'/edit/'. $credential->getId())
->addAction($acceptAction)
->addAction($declineAction);
$this->manager->notify($notification);
}
}

View file

@ -11,6 +11,7 @@ script('passman', 'vendor/angular-sanitize/angular-sanitize.min');
script('passman', 'vendor/angular-touch/angular-touch.min');
script('passman', 'vendor/angular-local-storage/angular-local-storage.min');
script('passman', 'vendor/angular-off-click/angular-off-click.min');
script('passman', 'vendor/angularjs-datetime-picker/angularjs-datetime-picker.min');
script('passman', 'vendor/ng-password-meter/ng-password-meter');
script('passman', 'vendor/sjcl/sjcl');
script('passman', 'vendor/zxcvbn/zxcvbn');
@ -64,6 +65,7 @@ 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');
style('passman', 'vendor/angularjs-datetime-picker/angularjs-datetime-picker');
style('passman', 'app');
?>

View file

@ -14,11 +14,27 @@
</div>
<label>Expire date</label>
<div>
<span datetime-picker ng-model="storedCredential.expire_time" class="link"
future-only ng-show="storedCredential.expire_time == 0"
close-on-select="false"
> No expire date set</span>
<span datetime-picker ng-model="storedCredential.expire_time" class="link"
future-only ng-show="storedCredential.expire_time != 0"
close-on-select="false"> {{ storedCredential.expire_time | date:'dd-MM-yyyy @ HH:mm:ss'}}</span>
</div>
<label>Renew interval</label>
<div>
<input type="number" ng-model="renewIntervalValue" min="0" ng-change="updateInterval(renewIntervalValue, renewIntervalModifier)">
<select ng-model="renewIntervalModifier" ng-change="updateInterval(renewIntervalValue, renewIntervalModifier)">
<option value="0">Disabled</option>
<option value="86400">Day(s)</option>
<option value="604800">Week(s)</option>
<option value="2592000">Month(s)</option>
<option value="31622400">Year(s)</option>
</select>
</div>
</div>
<div class="col-xs-12 col-md-7 col-lg-7">