Merge branch 'master' of github.com:nextcloud/passman

This commit is contained in:
Marcos Zuriaga 2016-09-23 22:49:05 +02:00
commit 3d950d3540
35 changed files with 1358 additions and 246 deletions

View file

@ -13,14 +13,41 @@ namespace OCA\Passman\AppInfo;
use OCP\Util;
use OCP\BackgroundJob;
use OCA\Passman\Notifier;
use OCA\Passman\Activity;
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'),
];
});
$manager = \OC::$server->getActivityManager();
$manager->registerExtension(function() {
return new Activity(
\OC::$server->getL10NFactory()
);
});
/**
* 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

@ -43,5 +43,13 @@ return [
['name' => 'file#getFile', 'url' => '/api/v2/file/{file_id}', 'verb' => 'GET'],
['name' => 'file#deleteFile', 'url' => '/api/v2/file/{file_id}', 'verb' => 'DELETE'],
//Sharing stuff
['name' => 'share#search', 'url' => '/api/v2/sharing/search', 'verb' => 'POST'],
//Internal API
['name' => 'internal#remind', 'url' => '/api/internal/notifications/remind/{credential_id}', 'verb' => 'POST'],
['name' => 'internal#read', 'url' => '/api/internal/notifications/read/{credential_id}', 'verb' => 'DELETE'],
]
];

View file

@ -15,19 +15,23 @@ use OCP\IRequest;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\ApiController;
use OCA\Passman\Service\CredentialService;
use OCA\Passman\Activity;
use OCA\Passman\Service\ActivityService;
class CredentialController extends ApiController {
private $userId;
private $credentialService;
private $activityService;
public function __construct($AppName,
IRequest $request,
$UserId,
CredentialService $credentialService) {
CredentialService $credentialService,
ActivityService $activityService) {
parent::__construct($AppName, $request);
$this->userId = $UserId;
$this->credentialService = $credentialService;
$this->activityService = $activityService;
}
/**
@ -63,6 +67,11 @@ class CredentialController extends ApiController {
);
$credential = $this->credentialService->createCredential($credential);
$link = ''; // @TODO create direct link to credential
$this->activityService->add(
Activity::SUBJECT_ITEM_CREATED_SELF, array($label, $this->userId),
'', array(),
$link, $this->userId, Activity::TYPE_ITEM_ACTION);
return new JSONResponse($credential);
}
@ -70,7 +79,7 @@ class CredentialController extends ApiController {
* @NoAdminRequired
*/
public function getCredential($credential_id) {
return;
return new JSONResponse($this->credentialService->getCredentialById($credential_id, $this->userId));
}
/**
@ -104,7 +113,37 @@ class CredentialController extends ApiController {
'hidden' => $hidden,
'otp' => $otp,
);
$storedCredential = $this->credentialService->getCredentialById($credential_id, $this->userId);
$link = ''; // @TODO create direct link to credential
if (($storedCredential->getDeleteTime() == 0) && $delete_time > 0) {
$this->activityService->add(
'item_deleted_self', array($label, $this->userId),
'', array(),
$link, $this->userId, Activity::TYPE_ITEM_ACTION);
} else if (($storedCredential->getDeleteTime() > 0) && $delete_time == 0) {
$this->activityService->add(
'item_recovered_self', array($label, $this->userId),
'', array(),
$link, $this->userId, Activity::TYPE_ITEM_ACTION);
} else if ($label != $storedCredential->getLabel()) {
$this->activityService->add(
'item_renamed_self', array($storedCredential->getLabel(), $label, $this->userId),
'', array(),
$link, $this->userId, Activity::TYPE_ITEM_ACTION);
} else {
$this->activityService->add(
'item_edited_self', array($label, $this->userId),
'', array(),
$link, $this->userId, Activity::TYPE_ITEM_ACTION);
}
$credential = $this->credentialService->updateCredential($credential);
return new JSONResponse($credential);
}
@ -112,9 +151,20 @@ class CredentialController extends ApiController {
* @NoAdminRequired
*/
public function deleteCredential($credential_id) {
return;
$credential = $this->credentialService->getCredentialById($credential_id, $this->userId);
if($credential){
$result = $this->credentialService->deleteCredential($credential);
$this->activityService->add(
'item_destroyed_self', array($credential->getLabel()),
'', array(),
'', $this->userId, Activity::TYPE_ITEM_ACTION);
} else {
$result = false;
}
return new JSONResponse($result);
}
/**
* @NoAdminRequired
*/

View file

@ -0,0 +1,57 @@
<?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\Controller;
use OCP\IRequest;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\ApiController;
use OCA\Passman\Service\CredentialService;
class InternalController extends ApiController {
private $userId;
private $credentialService;
public function __construct($AppName,
IRequest $request,
$UserId,
CredentialService $credentialService){
parent::__construct($AppName, $request);
$this->userId = $UserId;
$this->credentialService = $credentialService;
}
function remind($credential_id){
$credential = $this->credentialService->getCredentialById($credential_id, $this->userId);
$credential->setExpireTime(time()+ (24 * 60 * 60));
$this->credentialService->upd($credential);
$manager = \OC::$server->getNotificationManager();
$notification = $manager->createNotification();
$notification->setApp('passman')
->setObject('credential', $credential_id)
->setUser($this->userId);
$manager->markProcessed($notification);
}
function read($credential_id){
$credential = $this->credentialService->getCredentialById($credential_id, $this->userId);
$credential->setExpireTime(0);
$this->credentialService->upd($credential);
$manager = \OC::$server->getNotificationManager();
$notification = $manager->createNotification();
$notification->setApp('passman')
->setObject('credential', $credential_id)
->setUser($this->userId);
$manager->markProcessed($notification);
}
}

View file

@ -0,0 +1,106 @@
<?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\Controller;
use OCP\IRequest;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\ApiController;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\IUser;
use OCA\Passman\Service\VaultService;
use OCA\Passman\Service\CredentialService;
use OCA\Passman\Service\UserService;
class ShareController extends ApiController {
private $userId;
private $activityService;
private $groupManager;
private $userManager;
private $limit = 50;
private $offset = 0;
private $result = [];
public function __construct($AppName,
IRequest $request,
IUser $UserId,
IGroupManager $groupManager,
IUserManager $userManager,
ActivityService $activityService
) {
parent::__construct($AppName, $request);
$this->userId = $UserId;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->activityService = $activityService;
}
public function searchUsers($search) {
$users = array();
$usersTmp = $this->userManager->searchDisplayName($search, $this->limit, $this->offset);
foreach ($usersTmp as $user) {
$users[] = array(
'text' => $user->getDisplayName(),
'uid' => $user->getUID(),
'type' => 'user'
);
}
$this->result = array_merge($this->result, $users);
}
public function searchGroups($search){
$groups = array();
$groupsTmp = $this->groupManager->search($search, $this->limit, $this->offset);
foreach ($groupsTmp as $group) {
$groups[] = array(
'text' => $group->getGID(),
'uid' => $group->getGID(),
'type' => 'group'
);
}
$this->result = array_merge($this->result, $groups);
}
/**
* @NoAdminRequired
*/
public function search($search) {
$this->searchUsers($search);
$this->searchGroups($search);
return new JSONResponse($this->result);
}
public function share($credential){
$link = '';
$this->activityService->add(
'item_shared', array($credential->label, $this->userId),
'', array(),
$link, $this->userId, Activity::TYPE_ITEM_ACTION);
}
}

View file

@ -171,9 +171,9 @@
display: inline-block;
font-weight: bold; }
#app-content #app-content-wrapper .actions.creatable .searchboxContainer {
display: inline-block;
float: right;
margin-right: 10px; }
position: absolute;
right: 10px;
top: 0; }
#app-content #app-content-wrapper .actions.creatable .searchboxContainer .searchbox {
display: inline-block; }
#app-content #app-content-wrapper .credential-table {
@ -203,6 +203,8 @@
border-radius: 5px;
background-clip: padding-box;
/* stops bg color from leaking outside the border: */ }
#app-content #app-content-wrapper .credential-table tr td .tags .tag:last-child {
margin-right: 8px; }
#app-content #app-content-wrapper .credential-table tr td .icon-more {
display: inline-block;
float: right;
@ -352,6 +354,13 @@
#app-navigation > ul ul {
display: inherit !important; }
.sharing_table td:first-child {
width: 55%; }
.sharing_table td:first-child tags-input .tags {
width: 100%; }
.sharing_table td:first-child .autocomplete {
margin-top: 35px; }
.table {
width: 100%; }

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,74 @@
.angularjs-datetime-picker {
color: #333;
font: normal 14px sans-serif;
border: 1px solid #ddd;
display: inline-block;
background: #fff;
}
.angularjs-datetime-picker > .adp-month {
text-align: center;
line-height: 22px;
padding: 10px;
background: #fcfcfc;
text-transform: uppercase;
font-weight: bold;
border-bottom: 1px solid #ddd;
position: relative;
}
.angularjs-datetime-picker > .adp-month > button {
color: #555;
font: normal 14px sans-serif;
outline: none;
position: absolute;
background: transparent;
border: none;
cursor: pointer;
}
.angularjs-datetime-picker > .adp-month > button:hover {
color: #333;
}
.angularjs-datetime-picker > .adp-month > button.adp-prev {
left: 10px;
}
.angularjs-datetime-picker > .adp-month > button.adp-next {
right: 10px;
}
.angularjs-datetime-picker > .adp-days {
width: 210px; /* 30 x 7 */
margin: 10px;
text-align: center;
}
.angularjs-datetime-picker > .adp-days > .adp-day-of-week, .angularjs-datetime-picker > .adp-days > .adp-day {
box-sizing: border-box;
-moz-box-sizing: border-box;
border: 1px solid transparent;
width: 30px;
line-height: 28px;
float: left;
}
.angularjs-datetime-picker > .adp-days > .adp-day-of-week {
font-weight: bold;
}
.angularjs-datetime-picker > .adp-days > .adp-day:not(.selectable) {
opacity: 0.15;
cursor: default;
}
.angularjs-datetime-picker > .adp-days > .adp-day.selectable {
cursor: pointer;
}
.angularjs-datetime-picker > .adp-days > .adp-day.selected {
background: #e0e0e0;
}
.angularjs-datetime-picker > .adp-days > .adp-day.selectable:hover {
background: #eee;
}
.angularjs-datetime-picker > .adp-days:after {
content: '';
display: block;
clear: left;
height: 0;
}
.angularjs-datetime-picker input[type=range] {
width: 150px;
}

View file

@ -22,7 +22,8 @@ angular
'ngPasswordMeter',
'ngclipboard',
'xeditable',
'ngTagsInput'
'ngTagsInput',
'angularjs-datetime-picker'
])
.config(function ($routeProvider) {
$routeProvider
@ -108,5 +109,7 @@ jQuery(document).ready(function () {
}
};
$(window).resize(_.debounce(adjustControlsWidth, 256));
adjustControlsWidth()
setTimeout(function(){
adjustControlsWidth()
},200)
});

View file

@ -95,7 +95,7 @@ angular.module('passmanApp')
if (notification) {
NotificationService.hideNotification(notification);
}
NotificationService.showNotification('Credential recovered <a class="undoRestore" data-item-id="' + credential.credential_id + '">[Undo]</a>', 7500,
NotificationService.showNotification('Credential recovered <a class="undoRestore" data-item-id="' + credential.credential_id + '">[Undo]</a>', 5000,
function () {
CredentialService.updateCredential(_credential).then(function (result) {
notification = false;
@ -105,6 +105,19 @@ angular.module('passmanApp')
};
$scope.destroyCredential = function(credential){
var _credential = angular.copy(credential);
CredentialService.destroyCredential(_credential.credential_id).then(function (result) {
for (var i = 0; i < $scope.credentials.length; i++) {
if ($scope.credentials[i].credential_id == credential.credential_id) {
$scope.credentials.splice(i,1);
NotificationService.showNotification('Credential destroyed', 5000);
break;
}
}
});
};
$scope.itemFilter = {
label: ''
};
@ -168,14 +181,10 @@ angular.module('passmanApp')
$rootScope.$on('logout', function () {
console.log('Logout received, clean up');
$scope.credentials = [];
if ($scope.hasOwnProperty('$parent')) {
if ($scope.$parent.hasOwnProperty('selectedVault')) {
$scope.$parent.selectedVault = false;
}
}
$scope.active_vault = null;
$scope.credentials = [];
// $scope.$parent.selectedVault = false;
});
var fetchCredentials = function () {

View file

@ -10,198 +10,211 @@
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) {
CredentialService.getCredential($routeParams.credential_id).then(function(result){
$scope.storedCredential = CredentialService.decryptCredential(angular.copy(result));
});
} 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
//@TODO When credential is expired and has renew interval set, calc new expire time.
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

@ -10,7 +10,13 @@
angular.module('passmanApp')
.controller('MainCtrl', ['$scope', '$rootScope', function ($scope, $rootScope) {
$scope.selectedVault = false;
$rootScope.$on('app_menu', function(evt, shown){
$scope.app_sidebar = shown;
});
$rootScope.$on('logout', function () {
$scope.selectedVault = false;
})
}]);

View file

@ -10,7 +10,7 @@
angular.module('passmanApp')
.controller('MenuCtrl', ['$scope', 'VaultService', 'SettingsService', '$location', '$rootScope', 'TagService',
function ($scope, VaultService, SettingsService, $location, $rootScope, TagService) {
$scope.logout = function () {
$rootScope.logout = function () {
SettingsService.setSetting('defaultVaultPass', false);
$rootScope.$broadcast('logout');
$location.path('/');

View file

@ -8,7 +8,7 @@
* Controller of the passmanApp
*/
angular.module('passmanApp')
.controller('ShareCtrl', ['$scope', 'VaultService', 'CredentialService', 'SettingsService', '$location', '$routeParams', function ($scope, VaultService, CredentialService, SettingsService, $location, $routeParams) {
.controller('ShareCtrl', ['$scope', 'VaultService', 'CredentialService', 'SettingsService', '$location', '$routeParams', 'ShareService', function ($scope, VaultService, CredentialService, SettingsService, $location, $routeParams, ShareService) {
$scope.active_vault = VaultService.getActiveVault();
$scope.tabs = [{
@ -60,21 +60,9 @@ angular.module('passmanApp')
};
$scope.searchUsersAndGroups = function($query){
};
$scope.share_settings = {
credentialSharedWithUserAndGroup:[
{
userId: 'someuser',
accessLevel: 'CAN_VIEW'
},
{
userId: 'someuser',
accessLevel: 'CAN_EDIT'
}
]
credentialSharedWithUserAndGroup:[]
};
$scope.accessLevels = [
@ -86,8 +74,30 @@ angular.module('passmanApp')
label: 'Can view',
value: 'CAN_VIEW'
}
]
];
$scope.inputSharedWith = [];
$scope.selectedAccessLevel = 'CAN_VIEW';
$scope.searchUsers = function($query){
return ShareService.search($query)
};
$scope.shareWith = function(shareWith, selectedAccessLevel){
$scope.inputSharedWith = [];
if(shareWith.length > 0) {
for (var i = 0; i < shareWith.length; i++) {
$scope.share_settings.credentialSharedWithUserAndGroup.push(
{
userId: shareWith[i].uid,
displayName: shareWith[i].text,
type: shareWith[i].type,
accessLevel: selectedAccessLevel
}
)
}
}
}
}]);

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) {
@ -72,6 +75,26 @@ angular.module('passmanApp')
}
});
},
getCredential: function(id){
var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials/' + id);
return $http.get(queryUrl).then(function (response) {
if (response.data) {
return response.data;
} else {
return response;
}
});
},
destroyCredential: function(id){
var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials/' + id);
return $http.delete(queryUrl).then(function (response) {
if (response.data) {
return response.data;
} else {
return response;
}
});
},
encryptCredential: function (credential) {
for (var i = 0; i < _encryptedFields.length; i++) {
var field = _encryptedFields[i];

View file

@ -0,0 +1,25 @@
'use strict';
/**
* @ngdoc service
* @name passmanApp.ShareService
* @description
* # ShareService
* Service in the passmanApp.
*/
angular.module('passmanApp')
.service('ShareService', ['$http', function ($http) {
var _tags = [];
return {
search: function (string) {
var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/search');
return $http.post(queryUrl, {search: string}).then(function (response) {
if (response.data) {
return response.data;
} else {
return response;
}
});
}
}
}]);

View file

@ -3,7 +3,7 @@ angular.module('templates-main', ['views/edit_credential.html', 'views/partials/
angular.module('views/edit_credential.html', []).run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('views/edit_credential.html',
'<div id="controls"><div class="actions creatable"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a><i class="fa fa-home"></i></a></div><div class="crumb svg" data-dir="/Test"><a>{{active_vault.name}}</a></div><div class="crumb svg last" data-dir="/Test"><a ng-if="storedCredential.credential_id">Edit credential "{{storedCredential.label}}"</a> <a ng-if="!storedCredential.credential_id">Create new credential</a></div></div></div></div><ul class="tab_header"><li ng-repeat="tab in tabs track by $index" class="tab" ng-class="{active:isActiveTab(tab)}" ng-click="onClickTab(tab)">{{tab.title}}</li></ul><div class="tab_container edit_credential"><div ng-include="currentTab.url"></div><button ng-click="saveCredential()">Save</button> <button ng-click="cancel()">Cancel</button></div>');
'<div id="controls"><div class="actions creatable"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a ng-click="logout()"><i class="fa fa-home"></i></a></div><div class="crumb svg" data-dir="/Test"><a ng-click="cancel()">{{active_vault.name}}</a></div><div class="crumb svg last" data-dir="/Test"><a ng-if="storedCredential.credential_id">Edit credential "{{storedCredential.label}}"</a> <a ng-if="!storedCredential.credential_id">Create new credential</a></div></div></div></div><ul class="tab_header"><li ng-repeat="tab in tabs track by $index" class="tab" ng-class="{active:isActiveTab(tab)}" ng-click="onClickTab(tab)">{{tab.title}}</li></ul><div class="tab_container edit_credential"><div ng-include="currentTab.url"></div><button ng-click="saveCredential()">Save</button> <button ng-click="cancel()">Cancel</button></div>');
}]);
angular.module('views/partials/forms/edit_credential/basics.html', []).run(['$templateCache', function($templateCache) {
@ -33,13 +33,15 @@ 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) {
'use strict';
$templateCache.put('views/partials/forms/share_credential/basics.html',
'<div class="row"><div class="col-xs-12 col-md-6"><div><table class="table"><thead><tr><td><input type="text" placeholder="Search user or groups"></td><td><select><option ng-repeat="lvl in accessLevels" value="{{lvl.value}}">{{lvl.label}}</option></select><button class="button">+</button></td></tr></thead></table></div></div></div><div class="row"><div class="col-xs-12 col-md-6"><table class="table shared_table"><thead><tr><td>User / group</td><td>Access</td></tr></thead><tr ng-repeat="user in share_settings.credentialSharedWithUserAndGroup"><td>{{user.userId}}</td><td>{{user.accessLevel}}</td></tr></table></div></div>');
'<div class="row"><div class="col-xs-12 col-md-6"><div><table class="table sharing_table"><thead><tr><td><tags-input ng-model="inputSharedWith" replace-spaces-with-dashes="false" add-from-autocomplete-only="true" placeholder="Search users or groups..."><auto-complete source="searchUsers($query)" min-length="0" template="autocomplete-template"></auto-complete></tags-input></td><td><select ng-model="selectedAccessLevel"><option ng-repeat="lvl in accessLevels" value="{{lvl.value}}">{{lvl.label}}</option></select><button class="button" ng-click="shareWith(inputSharedWith, selectedAccessLevel)">+</button></td></tr></thead></table></div></div></div><div class="row"><div class="col-xs-12 col-md-6"><table class="table shared_table" ng-show="share_settings.credentialSharedWithUserAndGroup.length > 0"><thead><tr><td>User / group</td><td>Access</td></tr></thead><tr ng-repeat="user in share_settings.credentialSharedWithUserAndGroup"><td><i class="fa fa-user" ng-if="user.type === \'user\'"></i> <i class="fa fa-group" ng-if="user.type === \'group\'"></i> {{user.userId}}</td><td>{{user.accessLevel}}</td></tr></table></div></div><script type="text/ng-template" id="autocomplete-template"><i class="fa fa-user" ng-if="data.type === \'user\'"></i>\n' +
' <i class="fa fa-group" ng-if="data.type === \'group\'"></i>\n' +
' {{data.text}}</script>');
}]);
angular.module('views/partials/forms/share_credential/expire_settings.html', []).run(['$templateCache', function($templateCache) {
@ -57,13 +59,13 @@ angular.module('views/partials/password-meter.html', []).run(['$templateCache',
angular.module('views/share_credential.html', []).run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('views/share_credential.html',
'<div id="controls"><div class="actions creatable"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a><i class="fa fa-home"></i></a></div><div class="crumb svg" data-dir="/Test"><a>{{active_vault.name}}</a></div><div class="crumb svg last" data-dir="/Test"><a ng-if="storedCredential.credential_id">Share credential "{{storedCredential.label}}"</a></div></div></div></div><ul class="tab_header"><li ng-repeat="tab in tabs track by $index" class="tab" ng-class="{active:isActiveTab(tab)}" ng-click="onClickTab(tab)">{{tab.title}}</li></ul><div class="tab_container edit_credential"><div ng-include="currentTab.url"></div><button>Share</button> <button ng-click="cancel()">Cancel</button></div>');
'<div id="controls"><div class="actions creatable"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a ng-click="logout()"><i class="fa fa-home"></i></a></div><div class="crumb svg" data-dir="/Test"><a ng-click="cancel()">{{active_vault.name}}</a></div><div class="crumb svg last" data-dir="/Test"><a ng-if="storedCredential.credential_id">Share credential "{{storedCredential.label}}"</a></div></div></div></div><ul class="tab_header"><li ng-repeat="tab in tabs track by $index" class="tab" ng-class="{active:isActiveTab(tab)}" ng-click="onClickTab(tab)">{{tab.title}}</li></ul><div class="tab_container edit_credential"><div ng-include="currentTab.url"></div><button>Share</button> <button ng-click="cancel()">Cancel</button></div>');
}]);
angular.module('views/show_vault.html', []).run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('views/show_vault.html',
'<div id="controls"><div class="breadcrumb"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a><i class="fa fa-home"></i></a></div><div class="crumb svg last"><a>{{active_vault.name}}</a></div></div></div><div class="actions creatable"><span ng-click="menuOpen = !menuOpen" class="button new" ng-init="menuOpen = false" off-click="menuOpen = false;"><span>+</span></span><div class="actionList popovermenu bubble menu" ng-show="menuOpen"><ul><li><span ng-click="addCredential()" class="menuitem action"><span class="icon icon-rename"></span> <span>New credential</span></span></li><li><span href="#" class="menuitem action"><span class="icon icon-shared"></span> <span>New shared credential</span></span></li></ul></div><div class="searchboxContainer"><input type="text" ng-model="itemFilter.label" class="searchbox" placeholder="Search credential..."></div><span class="title" ng-if="delete_time">Showing deleted since: <span ng-if="delete_time == 1">All time</span> <span ng-if="delete_time > 1">{{delete_time | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span></span></div></div><div off-click="closeSelected()"><table class="credential-table" ng-init="menuOpen = false;"><tr ng-repeat="credential in credentials | filter:itemFilter | tagFilter:selectedtags | as:this:\'filtered_credentials\'" ng-if="credential.hidden == 0 && showCredentialRow(credential)" ng-click="selectCredential(credential)" ng-class="{\'selected\': selectedCredential.credential_id == credential.credential_id}"><td><span class="icon"><i class="fa fa-lock"></i></span> <span class="label">{{credential.label}}</span> <span class="tags"><span class="tag" ng-repeat="tag in credential.tags_raw">{{tag.text}}</span></span></td></tr></table><div id="app-sidebar" class="detailsView scroll-container app_sidebar" ng-show="selectedCredential"><span class="close icon-close" ng-click="closeSelected()" alt="Close"></span><table><tr ng-show="selectedCredential.label"><td>Label</td><td>{{selectedCredential.label}}</td></tr><tr ng-show="selectedCredential.username"><td>Account</td><td><span credential-field value="selectedCredential.username"></span></td></tr><tr ng-show="selectedCredential.password"><td>Password</td><td><span credential-field value="selectedCredential.password" secret="\'true\'"></span></td></tr><tr ng-show="selectedCredential.otp.secret"><td>OTP</td><td><span otp-generator secret="selectedCredential.otp.secret"></span></td></tr><tr ng-show="selectedCredential.email"><td>E-mail</td><td><span credential-field value="selectedCredential.email"></span></td></tr><tr ng-show="selectedCredential.url"><td>URL</td><td><span credential-field value="selectedCredential.url"></span></td></tr><tr ng-show="selectedCredential.files.length > 0"><td>Files</td><td><div ng-repeat="file in selectedCredential.files" class="link" ng-click="downloadFile(file)">{{file.filename}} ({{file.size | bytes}})</div></td></tr><tr ng-repeat="field in selectedCredential.custom_fields"><td>{{field.label}}</td><td><span credential-field value="field.value" secret="field.secret"></span></td></tr><tr ng-show="selectedCredential.changed"><td>Changed</td><td>{{selectedCredential.changed * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr><tr ng-show="selectedCredential.created"><td>Created</td><td>{{selectedCredential.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr></table><div class="tags"><span class="tag" ng-repeat="tag in selectedCredential.tags">{{tag.text}}</span></div><div ng-show="selectedCredential"><span class="button" ng-click="editCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-edit"></span> Edit</span> <span class="button" ng-click="deleteCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-trash"></span> Delete</span> <span class="button" ng-if="selectedCredential.delete_time > 0" ng-click="recoverCredential(selectedCredential)"><span class="fa fa-recycle"></span> Recover</span> <span class="button" ng-if="selectedCredential.delete_time == 0" ng-click="shareCredential(selectedCredential)"><span class="fa fa-share"></span> Share</span></div></div></div>');
'<div id="controls"><div class="breadcrumb"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a><i class="fa fa-home"></i></a></div><div class="crumb svg last"><a>{{active_vault.name}}</a></div></div></div><div class="actions creatable"><span ng-click="menuOpen = !menuOpen" class="button new" ng-init="menuOpen = false" off-click="menuOpen = false;"><span>+</span></span><div class="actionList popovermenu bubble menu" ng-show="menuOpen"><ul><li><span ng-click="addCredential()" class="menuitem action"><span class="icon icon-rename"></span> <span>New credential</span></span></li><li><span href="#" class="menuitem action"><span class="icon icon-shared"></span> <span>New shared credential</span></span></li></ul></div><div class="searchboxContainer"><input type="text" ng-model="itemFilter.label" class="searchbox" placeholder="Search credential..."></div><span class="title" ng-if="delete_time">Showing deleted since: <span ng-if="delete_time == 1">All time</span> <span ng-if="delete_time > 1">{{delete_time | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span></span></div></div><div off-click="closeSelected()"><table class="credential-table" ng-init="menuOpen = false;"><tr ng-repeat="credential in credentials | filter:itemFilter | tagFilter:selectedtags | as:this:\'filtered_credentials\'" ng-if="credential.hidden == 0 && showCredentialRow(credential)" ng-click="selectCredential(credential)" ng-class="{\'selected\': selectedCredential.credential_id == credential.credential_id}"><td><span class="icon"><i class="fa fa-lock"></i></span> <span class="label">{{credential.label}}</span> <span class="tags"><span class="tag" ng-repeat="tag in credential.tags_raw">{{tag.text}}</span></span></td></tr></table><div id="app-sidebar" class="detailsView scroll-container app_sidebar" ng-show="selectedCredential"><span class="close icon-close" ng-click="closeSelected()" alt="Close"></span><table><tr ng-show="selectedCredential.label"><td>Label</td><td>{{selectedCredential.label}}</td></tr><tr ng-show="selectedCredential.username"><td>Account</td><td><span credential-field value="selectedCredential.username"></span></td></tr><tr ng-show="selectedCredential.password"><td>Password</td><td><span credential-field value="selectedCredential.password" secret="\'true\'"></span></td></tr><tr ng-show="selectedCredential.otp.secret"><td>OTP</td><td><span otp-generator secret="selectedCredential.otp.secret"></span></td></tr><tr ng-show="selectedCredential.email"><td>E-mail</td><td><span credential-field value="selectedCredential.email"></span></td></tr><tr ng-show="selectedCredential.url"><td>URL</td><td><span credential-field value="selectedCredential.url"></span></td></tr><tr ng-show="selectedCredential.files.length > 0"><td>Files</td><td><div ng-repeat="file in selectedCredential.files" class="link" ng-click="downloadFile(file)">{{file.filename}} ({{file.size | bytes}})</div></td></tr><tr ng-repeat="field in selectedCredential.custom_fields"><td>{{field.label}}</td><td><span credential-field value="field.value" secret="field.secret"></span></td></tr><tr ng-show="selectedCredential.changed"><td>Changed</td><td>{{selectedCredential.changed * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr><tr ng-show="selectedCredential.created"><td>Created</td><td>{{selectedCredential.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr></table><div class="tags"><span class="tag" ng-repeat="tag in selectedCredential.tags">{{tag.text}}</span></div><div ng-show="selectedCredential"><span class="button" ng-click="editCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-edit"></span> Edit</span> <span class="button" ng-click="deleteCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-trash"></span> Delete</span> <span class="button" ng-if="selectedCredential.delete_time > 0" ng-click="recoverCredential(selectedCredential)"><span class="fa fa-recycle"></span> Recover</span> <span class="button" ng-if="selectedCredential.delete_time > 0" ng-click="destroyCredential(selectedCredential)"><span class="fa fa-bomb"></span> Destroy</span> <span class="button" ng-if="selectedCredential.delete_time == 0" ng-click="shareCredential(selectedCredential)"><span class="fa fa-share"></span> Share</span></div></div></div>');
}]);
angular.module('views/vaults.html', []).run(['$templateCache', function($templateCache) {

File diff suppressed because one or more lines are too long

268
lib/Activity.php Normal file
View file

@ -0,0 +1,268 @@
<?php
/**
* ownCloud - 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;
class Activity implements \OCP\Activity\IExtension {
const TYPE_ITEM_ACTION = 'passman_item_action';
const TYPE_ITEM_EXPIRED = 'passman_item_expired';
const TYPE_ITEM_SHARED = 'passman_item_shared';
const TYPE_ITEM_RENAMED = 'passman_item_renamed';
const SUBJECT_ITEM_CREATED = 'item_created';
const SUBJECT_ITEM_CREATED_SELF = 'item_created_self';
const SUBJECT_ITEM_EDITED = 'item_edited';
const SUBJECT_ITEM_EDITED_SELF = 'item_edited_self';
const SUBJECT_APPLY_REV = 'item_apply_revision';
const SUBJECT_APPLY_REV_SELF = 'item_apply_revision_self';
const SUBJECT_ITEM_DELETED = 'item_deleted';
const SUBJECT_ITEM_DELETED_SELF = 'item_deleted_self';
const SUBJECT_ITEM_RECOVERED = 'item_recovered';
const SUBJECT_ITEM_RECOVERED_SELF = 'item_recovered_self';
const SUBJECT_ITEM_DESTROYED = 'item_destroyed';
const SUBJECT_ITEM_DESTROYED_SELF = 'item_destroyed_self';
const SUBJECT_ITEM_EXPIRED = 'item_expired';
const SUBJECT_ITEM_SHARED = 'item_shared';
const SUBJECT_ITEM_RENAMED = 'item_renamed';
const SUBJECT_ITEM_RENAMED_SELF = 'item_renamed_self';
/**
* The extension can return an array of additional notification types.
* If no additional types are to be added false is to be returned
*
* @param string $languageCode
* @return array|false
*/
public function getNotificationTypes($languageCode) {
$l = \OC::$server->getL10N('passman', $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'),
self::TYPE_ITEM_SHARED => $l->t('A Passman item has been shared'),
self::TYPE_ITEM_RENAMED => $l->t('A Passman item has been renamed')
);
}
/**
* The extension can filter the types based on the filter if required.
* In case no filter is to be applied false is to be returned unchanged.
*
* @param array $types
* @param string $filter
* @return array|false
*/
public function filterNotificationTypes($types, $filter) {
return $types;
}
/**
* For a given method additional types to be displayed in the settings can be returned.
* In case no additional types are to be added false is to be returned.
*
* @param string $method
* @return array|false
*/
public function getDefaultTypes($method) {
if ($method === 'stream') {
return array(
self::TYPE_ITEM_ACTION,
self::TYPE_ITEM_EXPIRED,
self::TYPE_ITEM_SHARED,
self::TYPE_ITEM_EXPIRED,
self::TYPE_ITEM_RENAMED,
);
}
if ($method === 'email') {
return array(
self::TYPE_ITEM_EXPIRED,
);
}
return false;
}
/**
* The extension can translate a given message to the requested languages.
* If no translation is available false is to be returned.
*
* @param string $app
* @param string $text
* @param array $params
* @param boolean $stripPath
* @param boolean $highlightParams
* @param string $languageCode
* @return string|false
*/
public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) {
$l = new \OC_L10N('passman', $languageCode);
if ($app === 'passman') {
switch ($text) {
case self::SUBJECT_ITEM_CREATED:
return $l->t('%1$s has been created by %2$s', $params)->__toString();
case self::SUBJECT_ITEM_CREATED_SELF:
return $l->t('You created %1$s', $params)->__toString();
case self::SUBJECT_ITEM_EDITED:
return $l->t('%1$s has been updated by %2$s', $params)->__toString();
case self::SUBJECT_ITEM_EDITED_SELF:
return $l->t('You updated %1$s', $params)->__toString();
case self::SUBJECT_APPLY_REV:
return $l->t('%2$s has revised %1$s to the revision of %3$s', $params)->__toString();
case self::SUBJECT_APPLY_REV_SELF:
return $l->t('You reverted %1$s back to the revision of %3$s', $params)->__toString();
case self::SUBJECT_ITEM_RENAMED:
return $l->t('%3$s has renamed %1$s to %2$s', $params)->__toString();
case self::SUBJECT_ITEM_RENAMED_SELF:
return $l->t('You renamed %1$s to %2$s', $params)->__toString();
case self::SUBJECT_ITEM_DELETED:
return $l->t('%1$s has been deleted by %2$s', $params)->__toString();
case self::SUBJECT_ITEM_DELETED_SELF:
return $l->t('You deleted %1$s', $params)->__toString();
case self::SUBJECT_ITEM_RECOVERED:
return $l->t('%1$s has been recovered by %2$s', $params)->__toString();
case self::SUBJECT_ITEM_RECOVERED_SELF:
return $l->t('You recovered %1$s', $params)->__toString();
case self::SUBJECT_ITEM_DESTROYED:
return $l->t('%1$s has been permanently deleted by %2$s', $params)->__toString();
case self::SUBJECT_ITEM_DESTROYED_SELF:
return $l->t('You permanently deleted %1$s', $params)->__toString();
case self::SUBJECT_ITEM_EXPIRED:
return $l->t('The password of %1$s has expired, renew it now.', $params)->__toString();
case self::SUBJECT_ITEM_SHARED:
return $l->t('%s has been shared', $params)->__toString();
}
}
return false;
}
/**
* The extension can define the type of parameters for translation
*
* Currently known types are:
* * file => will strip away the path of the file and add a tooltip with it
* * username => will add the avatar of the user
*
* @param string $app
* @param string $text
* @return array|false
*/
public function getSpecialParameterList($app, $text) {
if ($app === 'passman') {
switch ($text) {
case self::SUBJECT_ITEM_CREATED:
case self::SUBJECT_ITEM_CREATED_SELF:
case self::SUBJECT_ITEM_EDITED:
case self::SUBJECT_ITEM_EDITED_SELF:
case self::SUBJECT_ITEM_DELETED:
case self::SUBJECT_ITEM_DELETED_SELF:
case self::SUBJECT_ITEM_RECOVERED:
case self::SUBJECT_ITEM_RECOVERED_SELF:
case self::SUBJECT_ITEM_DESTROYED:
case self::SUBJECT_ITEM_DESTROYED_SELF:
return array(
0 => 'passman',
1 => 'username',
);
case self::SUBJECT_APPLY_REV:
case self::SUBJECT_APPLY_REV_SELF:
return array(
0 => 'passman',
1 => 'username',
2 => '', //unknown
);
case self::SUBJECT_ITEM_EXPIRED:
case self::SUBJECT_ITEM_RENAMED_SELF:
case self::SUBJECT_ITEM_RENAMED:
case self::SUBJECT_ITEM_SHARED:
return array(
0 => 'passman',
);
}
}
return false;
}
/**
* A string naming the css class for the icon to be used can be returned.
* If no icon is known for the given type false is to be returned.
*
* @param string $type
* @return string|false
*/
public function getTypeIcon($type) {
switch ($type) {
case self::TYPE_ITEM_ACTION:
case self::TYPE_ITEM_EXPIRED:
return 'icon-password';
case self::TYPE_ITEM_SHARED:
return 'icon-share';
case self::TYPE_ITEM_RENAMED:
return '';
}
return false;
}
/**
* The extension can define the parameter grouping by returning the index as integer.
* In case no grouping is required false is to be returned.
*
* @param array $activity
* @return integer|false
*/
public function getGroupParameter($activity) {
return false;
}
/**
* The extension can define additional navigation entries. The array returned has to contain two keys 'top'
* and 'apps' which hold arrays with the relevant entries.
* If no further entries are to be added false is no be returned.
*
* @return array|false
*/
public function getNavigation() {
$l = \OC::$server->getL10N('passman');
return array(
'top' => array(),
'apps' => array(
array(
'id' => 'passman',
'name' => (string) $l->t('Passwords'),
'url' => '',//FIXME: $this->URLGenerator->linkToRoute('activity.Activities.showList', array('filter' => 'passman')),
),
),
);
}
/**
* The extension can check if a customer filter (given by a query string like filter=abc) is valid or not.
*
* @param string $filterValue
* @return boolean
*/
public function isFilterValid($filterValue) {
return $filterValue === 'passman';
}
/**
* For a given filter the extension can specify the sql query conditions including parameters for that query.
* In case the extension does not know the filter false is to be returned.
* The query condition and the parameters are to be returned as array with two elements.
* E.g. return array('`app` = ? and `message` like ?', array('mail', 'ownCloud%'));
*
* @param string $filter
* @return array|false
*/
public function getQueryForFilter($filter) {
if ($filter === 'passman') {
return array('`app` = ?', array('passman'));
}
return false;
}
}

View file

@ -14,7 +14,13 @@ use OC\Files\View;
use OCA\Passman\Controller\CredentialController;
use OCA\Passman\Controller\PageController;
use OCA\Passman\Controller\ShareController;
use OCA\Passman\Controller\VaultController;
use OCA\Passman\Service\ActivityService;
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;
@ -23,6 +29,7 @@ class Application extends App {
public function __construct () {
parent::__construct('passman');
$container = $this->getContainer();
$server = $container->getServer();
// Allow automatic DI for the View, until we migrated to Nodes API
$container->registerService(View::class, function() {
return new View('');
@ -30,11 +37,58 @@ class Application extends App {
$container->registerService('isCLI', function() {
return \OC::$CLI;
});
/**
* Controllers
*/
$container->registerService('ShareController', function($c) {
$container = $this->getContainer();
$server = $container->getServer();
return new ShareController(
$c->query('AppName'),
$c->query('Request'),
$server->getUserSession()->getUser(),
$server->getGroupManager(),
$server->getUserManager(),
$server->getShareManager(),
$server->getURLGenerator(),
$server->getL10N($c->query('AppName')),
$c->query('ActivityService')
);
});
/** Cron **/
$container->registerService('CronService', function ($c) {
return new CronService(
$c->query('CredentialService'),
$c->query('Logger'),
$c->query('Utils'),
$c->query('NotificationService'),
$c->query('ActivityService'),
$c->query('ServerContainer')->getDb()
);
});
$container->registerService('Db', function () {
return new Db();
});
$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('ActivityService', ActivityService::class);
$container->registerAlias('Utils', Utils::class);
}
/**
* Register the navigation entry
*/

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,18 @@ 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 getCredentialById($credential_id, $user_id){
$sql = 'SELECT * FROM `*PREFIX*passman_credentials` ' .
'WHERE `id` = ? and `user_id` = ? ';
return $this->findEntity($sql,[$credential_id, $user_id]);
}
public function create($raw_credential){
$credential = new Credential();
@ -58,7 +70,7 @@ class CredentialMapper extends Mapper {
return parent::insert($credential);
}
public function update($raw_credential){
public function updateCredential($raw_credential){
if(!$raw_credential['guid']){
$raw_credential['guid'] = $this->utils->GUID();
}
@ -90,4 +102,11 @@ class CredentialMapper extends Mapper {
return parent::update($credential);
}
public function deleteCredential(Credential $credential){
$this->delete($credential);
}
public function upd(Credential $credential){
$this->update($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, click here to update the credential.', $notification->getSubjectParameters())
);
// Deal with the actions for a known subject
foreach ($notification->getActions() as $action) {
switch ($action->getLabel()) {
case 'remind':
$action->setParsedLabel(
(string) $l->t('Remind me later')
);
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

@ -0,0 +1,67 @@
<?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 ActivityService {
private $manager;
public function __construct() {
$this->manager = \OC::$server->getActivityManager();
}
/**
* @subject = One of these: item_created, item_edited, item_apply_revision
* item_deleted, item_recovered, item_destroyed,
* item_expired, item_shared
*
*
*
*
* @subjectParams = Subject | Subject params
* item_created = array($itemName,$user)
* item_edited = array($itemName,$user)
* item_apply_revision = array($itemName,$user,$revision);
* item_deleted = array($itemName,$user)
* item_recovered = array($itemName,$user)
* item_destroyed = array($itemName,$user)
* item_expired = array($itemName)
* item_shared = array($itemName)
* @message = Custom message (not needed)
* @messageParams = Message params (not needed)
* @link = will be -> <ownCloud>/apps/activity/$link
* @user = Target user
* @type = Can be passman_password or passman_password_shared
* @priority = Int -> [10,20,30,40,50]
*/
public function add($subject,$subjectParams=array(),
$message='',$messageParams=array(),
$link='',$user=null,$type='') {
$activity = $this->manager->generateEvent();
$activity->setType($type);
$activity->setApp('passman');
$activity->setSubject($subject, $subjectParams);
$activity->setLink($link);
$activity->setAffectedUser($user);
$activity->setAuthor($user);
$activity->setTimestamp(time());
$activity->setMessage($message, $messageParams);
print_r($this->manager->publish($activity));
return array('success'=>'ok');
}
}

View file

@ -30,10 +30,25 @@ class CredentialService {
}
public function updateCredential($credential) {
return $this->credentialMapper->update($credential);
return $this->credentialMapper->updateCredential($credential);
}
public function upd($credential) {
return $this->credentialMapper->upd($credential);
}
public function getCredentialsByVaultId($vault_id, $user_id){
public function deleteCredential($credential){
return $this->credentialMapper->deleteCredential($credential);
}
public function getCredentialsByVaultId($vault_id, $user_id) {
return $this->credentialMapper->getCredentialsByVaultId($vault_id, $user_id);
}
public function getExpiredCredentials($timestamp) {
return $this->credentialMapper->getExpiredCredentials($timestamp);
}
public function getCredentialById($credential_id, $user_id){
return $this->credentialMapper->getCredentialById($credential_id, $user_id);
}
}

View file

@ -0,0 +1,57 @@
<?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;
use OCA\Passman\Activity;
use OCP\IDBConnection;
class CronService {
private $credentialService;
private $logger;
private $utils;
private $notificationService;
private $activityService;
private $db;
public function __construct(CredentialService $credentialService, ILogger $logger, Utils $utils, NotificationService $notificationService, ActivityService $activityService, $db) {
$this->credentialService = $credentialService;
$this->logger = $logger;
$this->utils = $utils;
$this->notificationService = $notificationService;
$this->activityService = $activityService;
$this->db = $db;
}
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){
$link = ''; // @TODO create direct link to credential
$sql = 'SELECT count(*) as rows from `*PREFIX*notifications` WHERE `subject`= \'credential_expired\' AND object_id=?';
$query = $this->db->prepareQuery($sql);
$query->bindParam(1, $credential->getId(), \PDO::PARAM_INT);
$result = $query->execute();
if($result->fetchRow()['rows'] === 0) {
$this->activityService->add(
Activity::SUBJECT_ITEM_EXPIRED, array($credential->getLabel(), $credential->getUserId()),
'', array(),
$link, $credential->getUserId(), Activity::TYPE_ITEM_ACTION);
$this->notificationService->credentialExpiredNotification($credential);
}
}
}
}

View file

@ -0,0 +1,53 @@
<?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){
$urlGenerator = \OC::$server->getURLGenerator();
$link = $urlGenerator->getAbsoluteURL($urlGenerator->linkTo('','index.php/apps/passman/#/vault/'. $credential->getVaultId() .'/edit/'. $credential->getId()));
$api = $urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'index.php/apps/passman'));
$notification = $this->manager->createNotification();
$remindAction = $notification->createAction();
$remindAction->setLabel('remind')
->setLink($api. '/api/internal/notifications/remind/'. $credential->getId() , 'POST');
$declineAction = $notification->createAction();
$declineAction->setLabel('ignore')
->setLink($api . '/api/internal/notifications/read/'. $credential->getId(), '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($link)
->addAction($declineAction)
->addAction($remindAction);
$this->manager->notify($notification);
}
}

View file

@ -35,9 +35,10 @@
font-weight: bold;
}
.searchboxContainer{
display: inline-block;
float: right;
margin-right: 10px;
position: absolute;
right: 10px;
top: 0;
.searchbox{
display: inline-block;
@ -74,6 +75,9 @@
margin-right: 3px;
@include border-radius(5px);
}
.tag:last-child{
margin-right: 8px;
}
}
.icon-more {
display: inline-block;

View file

@ -1,3 +1,19 @@
.sharing_table{
td:first-child{
width: 55%;
tags-input{
.tags {
width: 100%;
input {
//width: 90% !important;
}
}
}
.autocomplete{
margin-top: 35px;
}
}
}
.table{
width: 100%;
}

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');
@ -43,6 +44,7 @@ script('passman', 'app/services/fileservice');
script('passman', 'app/services/encryptservice');
script('passman', 'app/services/tagservice');
script('passman', 'app/services/notificationservice');
script('passman', 'app/services/shareservice');
script('passman', 'app/directives/passwordgen');
script('passman', 'app/directives/fileselect');
script('passman', 'app/directives/progressbar');
@ -63,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

@ -2,10 +2,10 @@
<div class="actions creatable">
<div class="breadcrumb">
<div class="crumb svg ui-droppable" data-dir="/">
<a><i class="fa fa-home"></i></a>
<a ng-click="logout()"><i class="fa fa-home"></i></a>
</div>
<div class="crumb svg" data-dir="/Test">
<a>{{active_vault.name}}</a>
<a ng-click="cancel()">{{active_vault.name}}</a>
</div>
<div class="crumb svg last" data-dir="/Test">
<a ng-if="storedCredential.credential_id">Edit credential

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">

View file

@ -1,19 +1,29 @@
<div class="row">
<div class="col-xs-12 col-md-6">
<div>
<table class="table">
<table class="table sharing_table">
<thead>
<tr>
<td><input type="text" placeholder="Search user or groups"></td>
<td>
<tags-input ng-model="inputSharedWith"
replace-spaces-with-dashes="false"
add-from-autocomplete-only="true"
placeholder="Search users or groups...">
<auto-complete source="searchUsers($query)"
min-length="0"
template="autocomplete-template"></auto-complete>
</tags-input>
</td>
<td>
<select>
<select ng-model="selectedAccessLevel">
<option ng-repeat="lvl in accessLevels"
value="{{lvl.value}}">
{{lvl.label}}
</option>
</select>
<button class="button">
<button class="button"
ng-click="shareWith(inputSharedWith, selectedAccessLevel)">
+
</button>
</td>
@ -25,7 +35,8 @@
</div>
<div class="row">
<div class="col-xs-12 col-md-6">
<table class="table shared_table">
<table class="table shared_table"
ng-show="share_settings.credentialSharedWithUserAndGroup.length > 0">
<thead>
<tr>
<td>User / group</td>
@ -33,9 +44,19 @@
</tr>
</thead>
<tr ng-repeat="user in share_settings.credentialSharedWithUserAndGroup">
<td>{{user.userId}}</td>
<td>
<i class="fa fa-user" ng-if="user.type === 'user'"></i>
<i class="fa fa-group" ng-if="user.type === 'group'"></i>
{{user.userId}}
</td>
<td>{{user.accessLevel}}</td>
</tr>
</table>
</div>
</div>
</div>
<script type="text/ng-template" id="autocomplete-template">
<i class="fa fa-user" ng-if="data.type === 'user'"></i>
<i class="fa fa-group" ng-if="data.type === 'group'"></i>
{{data.text}}
</script>

View file

@ -2,10 +2,10 @@
<div class="actions creatable">
<div class="breadcrumb">
<div class="crumb svg ui-droppable" data-dir="/">
<a><i class="fa fa-home"></i></a>
<a ng-click="logout()"><i class="fa fa-home"></i></a>
</div>
<div class="crumb svg" data-dir="/Test">
<a>{{active_vault.name}}</a>
<a ng-click="cancel()">{{active_vault.name}}</a>
</div>
<div class="crumb svg last" data-dir="/Test">
<a ng-if="storedCredential.credential_id">Share credential

View file

@ -173,6 +173,10 @@
ng-click="recoverCredential(selectedCredential)">
<span class="fa fa-recycle"></span> Recover
</span>
<span class="button" ng-if="selectedCredential.delete_time > 0"
ng-click="destroyCredential(selectedCredential)">
<span class="fa fa-bomb"></span> Destroy
</span>
<span class="button" ng-if="selectedCredential.delete_time == 0" ng-click="shareCredential(selectedCredential)">
<span class="fa fa-share"></span> Share
</span>