Add feature to request vault destructions

This commit is contained in:
brantje 2017-02-20 00:01:01 +01:00
parent 514cb569fb
commit fd8dcc6c1c
No known key found for this signature in database
GPG key ID: 5FF1D117F918687F
29 changed files with 667 additions and 43 deletions

View file

@ -29,6 +29,7 @@ module.exports = function (grunt) {
},
jshint: {
options: {
reporter: require('jshint-stylish'),
curly: false,
eqeqeq: true,
eqnull: true,

View file

@ -548,4 +548,44 @@
</field>
</declaration>
</table>
<table>
<name>*dbprefix*passman_delete_vault_request</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<notnull>true</notnull>
<autoincrement>true</autoincrement>
<unsigned>true</unsigned>
<primary>true</primary>
<length>8</length>
</field>
<field>
<name>vault_guid</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>reason</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>requested_by</name>
<type>text</type>
<notnull>false</notnull>
<length>64</length>
</field>
<field>
<name>created</name>
<type>integer</type>
<length>64</length>
<default>0</default>
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
</declaration>
</table>
</database>

View file

@ -18,7 +18,7 @@ For an demo of this app visit [https://demo.passman.cc](https://demo.passman.cc)
]]></description>
<licence>AGPL</licence>
<version>2.1.0</version>
<version>2.1.1</version>
<author homepage="https://github.com/brantje">Sander Brand</author>
<author homepage="https://github.com/animalillo">Marcos Zuriaga</author>
<namespace>Passman</namespace>

View file

@ -88,5 +88,9 @@ return [
//Admin routes
['name' => 'admin#searchUser', 'url' => '/admin/search', 'verb' => 'GET'],
['name' => 'admin#moveCredentials', 'url' => '/admin/move', 'verb' => 'POST'],
['name' => 'admin#requestDeletion', 'url' => '/admin/request-deletion/{vault_guid}', 'verb' => 'POST'],
['name' => 'admin#deleteRequestDeletion', 'url' => '/admin/request-deletion/{vault_guid}', 'verb' => 'DELETE'],
['name' => 'admin#listRequests', 'url' => '/admin/delete-requests', 'verb' => 'GET'],
['name' => 'admin#acceptRequestDeletion', 'url' => '/admin/accept-delete-request', 'verb' => 'POST'],
]
];

View file

@ -12,15 +12,19 @@
namespace OCA\Passman\Controller;
use OCA\Passman\Db\CredentialRevision;
use OCA\Passman\Db\DeleteVaultRequest;
use OCA\Passman\Service\CredentialRevisionService;
use OCA\Passman\Service\DeleteVaultRequestService;
use OCA\Passman\Service\FileService;
use OCA\Passman\Service\VaultService;
use OCA\Passman\Utility\Utils;
use OCP\IConfig;
use OCP\IRequest;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\ApiController;
use OCA\Passman\Service\CredentialService;
use \OCP\App;
use OCP\JSON;
class AdminController extends ApiController {
private $userId;
@ -28,6 +32,7 @@ class AdminController extends ApiController {
private $credentialService;
private $fileService;
private $revisionService;
private $deleteVaultRequestService;
private $config;
public function __construct($AppName,
@ -37,6 +42,7 @@ class AdminController extends ApiController {
CredentialService $credentialService,
FileService $fileService,
CredentialRevisionService $revisionService,
DeleteVaultRequestService $deleteVaultRequestService,
IConfig $config
) {
parent::__construct(
@ -50,30 +56,32 @@ class AdminController extends ApiController {
$this->credentialService = $credentialService;
$this->fileService = $fileService;
$this->revisionService = $revisionService;
$this->deleteVaultRequestService = $deleteVaultRequestService;
$this->config = $config;
}
public function searchUser($term){
public function searchUser($term) {
$um = \OC::$server->getUserManager();
$results = array();
$searchResult = $um->search($term);
foreach ($searchResult as $user){
foreach ($searchResult as $user) {
array_push($results, array(
"value" => $user->getUID(),
"label" => $user->getDisplayName() . ' ('. $user->getBackendClassName() .')',
"label" => $user->getDisplayName() . ' (' . $user->getBackendClassName() . ')',
));
}
return new JSONResponse($results);
}
public function moveCredentials($source_account, $destination_account){
public function moveCredentials($source_account, $destination_account) {
$vaults = $this->vaultService->getByUser($source_account);
foreach ($vaults as $vault) {
$credentials = $this->credentialService->getCredentialsByVaultId($vault->getId(), $source_account);
foreach($credentials as $credential){
foreach ($credentials as $credential) {
$revisions = $this->revisionService->getRevisions($credential->getId());
foreach ($revisions as $revision){
foreach ($revisions as $revision) {
$r = new CredentialRevision();
$r->setId($revision['revision_id']);
$r->setGuid($revision['guid']);
@ -94,10 +102,87 @@ class AdminController extends ApiController {
}
$files = $this->fileService->getFilesFromUser($source_account);
foreach($files as $file){
foreach ($files as $file) {
$file->setUserId($destination_account);
$this->fileService->updateFile($file);
}
return new JSONResponse(array('success'=> true));
return new JSONResponse(array('success' => true));
}
public function listRequests(){
$requests = $this->deleteVaultRequestService->getDeleteRequests();
$results = array();
foreach($requests as $request){
$r = $request->jsonSerialize();
$r['displayName'] = Utils::getNameByUid($request->getRequestedBy());
array_push($results, $r);
}
return new JSONResponse($results);
}
public function acceptRequestDeletion($vault_guid, $requested_by){
$req = $this->deleteVaultRequestService->getDeleteRequestForVault($vault_guid);
try{
$vault = $this->vaultService->getByGuid($vault_guid, $requested_by);
} catch (\Exception $e){
//Ignore
}
if(isset($vault)){
$credentials = $this->credentialService->getCredentialsByVaultId($vault->getId(), $requested_by);
foreach($credentials as $credential){
$revisions = $this->revisionService->getRevisions($credential->getId());
foreach($revisions as $revision){
$this->revisionService->deleteRevision($revision['revision_id'], $requested_by);
}
$this->credentialService->deleteCredential($credential);
}
$this->vaultService->deleteVault($vault_guid, $requested_by);
}
$this->deleteVaultRequestService->removeDeleteRequestForVault($req);
return new JSONResponse(array('result' => true));
}
/**
* @NoAdminRequired
*/
public function requestDeletion($vault_guid, $reason) {
$req = $this->deleteVaultRequestService->getDeleteRequestForVault($vault_guid);
if($req){
return new JSONResponse('Already exists');
}
$vault = $this->vaultService->getByGuid($vault_guid, $this->userId);
$result = false;
if ($vault) {
$delete_request = new DeleteVaultRequest();
$delete_request->setRequestedBy($this->userId);
$delete_request->setVaultGuid($vault->getGuid());
$delete_request->setReason($reason);
$delete_request->setCreated(time());
$result = $this->deleteVaultRequestService->createRequest($delete_request);
}
return new JSONResponse(array('result' => $result));
}
/**
* @NoAdminRequired
*/
public function deleteRequestDeletion($vault_guid) {
$delete_request = false;
$result = false;
try {
$delete_request = $this->deleteVaultRequestService->getDeleteRequestForVault($vault_guid);
} catch (\Exception $exception){
// Ignore it
}
if ($delete_request) {
$this->deleteVaultRequestService->removeDeleteRequestForVault($delete_request);
$result = true;
}
return new JSONResponse(array('result' => $result));
}
}

View file

@ -347,6 +347,8 @@ class TranslationController extends ApiController {
// templates/views/vaults.html
'last.access' => $this->trans->t('Last accessed'),
'never' => $this->trans->t('Never'),
'no.vaults' => $this->trans->t('No vaults found, why not create one?'),
'min.vault.key.strength' => $this->trans->t('Password strength must be at least: {{strength}}'),
@ -362,6 +364,20 @@ class TranslationController extends ApiController {
'auto.logout' => $this->trans->t('Logout of this vault automatically after: '),
'vault.decrypt' => $this->trans->t('Decrypt vault'),
'req.intro1' => $this->trans->t('Seems you lost the vault password and you\'re unable to login.'),
'req.intro2' => $this->trans->t('If you want this vault removed you can request removal of the vault here.'),
'req.intro3' => $this->trans->t('An admin then accept to the request (or not)'),
'request.deletion.warning' => $this->trans->t('After an admin destroy\'s this vault, all credentials will be lost'),
'request.deletion.reason' => $this->trans->t('Reason to request deletion (optional):'),
'request.deletion' => $this->trans->t('Request vault destruction'),
'request.deletion.accept' => $this->trans->t('Yes, request an admin to destroy this vault'),
'cancel.request.deletion' => $this->trans->t('Cancel destruction request'),
'deletion.requested' => $this->trans->t('Vault destruction requested'),
'deletion.removed' => $this->trans->t('Request removed'),
'delete.request.pending' => $this->trans->t('Destruction request pending'),
// templates/bookmarklet.php
'http.warning' => $this->trans->t('Warning! Adding credentials over http can be insecure!'),
'bm.active.vault' => $this->trans->t('Logged in to {{vault_name}}'),

View file

@ -11,6 +11,7 @@
namespace OCA\Passman\Controller;
use OCA\Passman\Service\DeleteVaultRequestService;
use OCA\Passman\Service\EncryptService;
use OCA\Passman\Service\SettingsService;
use OCA\Passman\Utility\NotFoundJSONResponse;
@ -27,12 +28,14 @@ class VaultController extends ApiController {
private $vaultService;
private $credentialService;
private $settings;
private $deleteVaultRequestService;
public function __construct($AppName,
IRequest $request,
$UserId,
VaultService $vaultService,
CredentialService $credentialService,
DeleteVaultRequestService $deleteVaultRequestService,
SettingsService $settings) {
parent::__construct(
$AppName,
@ -43,6 +46,7 @@ class VaultController extends ApiController {
$this->userId = $UserId;
$this->vaultService = $vaultService;
$this->credentialService = $credentialService;
$this->deleteVaultRequestService = $deleteVaultRequestService;
$this->settings = $settings;
}
@ -68,6 +72,7 @@ class VaultController extends ApiController {
'public_sharing_key' => $vault->getPublicSharingKey(),
'last_access' => $vault->getlastAccess(),
'challenge_password' => $credential->{$secret_field}(),
'delete_request_pending' => ($this->deleteVaultRequestService->getDeleteRequestForVault($vault->getGuid())) ? true : false
));
}
}
@ -109,7 +114,8 @@ class VaultController extends ApiController {
'public_sharing_key' => $vault->getPublicSharingKey(),
'sharing_keys_generated' => $vault->getSharingKeysGenerated(),
'vault_settings' => $vault->getVaultSettings(),
'last_access' => $vault->getlastAccess()
'last_access' => $vault->getlastAccess(),
'delete_request_pending' => ($this->deleteVaultRequestService->getDeleteRequestForVault($vault->getGuid())) ? true : false
);
$result['credentials'] = $credentials;

View file

@ -2,5 +2,12 @@
padding: 5px; }
#passwordSharingSettings #mover input[type="text"] {
width: 350px; }
#passwordSharingSettings #requests-table {
width: 100%; }
#passwordSharingSettings .link {
color: #0066ff !important;
cursor: pointer; }
#passwordSharingSettings .link:hover {
text-decoration: underline; }
/*# sourceMappingURL=admin.css.map */

View file

@ -1,6 +1,6 @@
{
"version": 3,
"mappings": "AAGM,wCAAE;EACA,OAAO,EAAE,GAAG;AAGhB,kDAAkB;EAChB,KAAK,EAAE,KAAK",
"mappings": "AAGM,wCAAE;EACA,OAAO,EAAE,GAAG;AAGhB,kDAAkB;EAChB,KAAK,EAAE,KAAK;AAGhB,wCAAe;EACb,KAAK,EAAE,IAAI;AAIb,8BAAK;EACH,KAAK,EAAE,kBAA2B;EAClC,MAAM,EAAE,OAAO;AAEjB,oCAAW;EACT,eAAe,EAAE,SAAS",
"sources": ["../sass/admin.scss"],
"names": [],
"file": "admin.css"

View file

@ -326,6 +326,13 @@
display: inline-block; }
.vault_wrapper .login_form .button {
margin-top: 10px; }
.vault_wrapper .reset_form {
padding: 16px; }
.vault_wrapper .reset_form label {
margin-top: 20px;
display: block; }
.vault_wrapper .reset_form input[type="text"] {
width: 100%; }
.vault_wrapper .login_opts {
margin-bottom: 10px; }

File diff suppressed because one or more lines are too long

View file

@ -67,13 +67,19 @@
.when('/vault/:vault_id/edit/:credential_id', {
templateUrl: 'views/edit_credential.html',
controller: 'CredentialEditCtrl'
}).when('/vault/:vault_id/:credential_id/share', {
templateUrl: 'views/share_credential.html',
controller: 'ShareCtrl'
}).when('/vault/:vault_id/:credential_id/revisions', {
templateUrl: 'views/credential_revisions.html',
controller: 'RevisionCtrl'
})
})
.when('/vault/:vault_id/:credential_id/share', {
templateUrl: 'views/share_credential.html',
controller: 'ShareCtrl'
})
.when('/vault/:vault_id/:credential_id/revisions', {
templateUrl: 'views/credential_revisions.html',
controller: 'RevisionCtrl'
})
.when('/vault/:vault_id/request-deletion', {
templateUrl: 'views/vault_req_deletion.html',
controller: 'RequestDeleteCtrl'
})
.when('/vault/:vault_id/settings', {
templateUrl: 'views/settings.html',
controller: 'SettingsCtrl'

View file

@ -85,37 +85,36 @@
prop: null
}
];
var tagMapper = function (t) {
return {text: t};
};
var rowToCredential = function (row) {
var _credential = PassmanImporter.newCredential();
for(var k = 0; k < $scope.import_fields.length; k++){
var field = $scope.import_fields[k];
if(field){
if(field === 'otp'){
_credential.otp.secret = row[k]
_credential.otp.secret = row[k];
} else if(field === 'custom_field'){
var key = ($scope.matched) ? $scope.parsed_csv[0][k] : 'Custom field '+ k;
_credential.custom_fields.push({
'label': key,
'value': row[k],
'secret': 0
})
});
} else if(field === 'tags'){
if( row[k]) {
console.log(row, k);
var tags = row[k].split(',');
console.log();
_credential.tags = tags.map(function (t) {
console.log(t);
return {text: t}
});
_credential.tags = tags.map(tagMapper);
}
} else{
_credential[field] = row[k];
}
}
}
return _credential
return _credential;
};
@ -204,6 +203,6 @@
$scope.updateExample = function () {
var start = ($scope.skipFirstRow) ? 1 : 0;
$scope.inspectCredential($scope.parsed_csv[start]);
}
};
}]);
}());

View file

@ -151,6 +151,10 @@
};
$scope.requestDeletion = function (vault) {
$location.path('/vault/' + vault.guid +'/request-deletion');
};
var _loginToVault = function (vault, vault_key) {
var _vault = angular.copy(vault);
_vault.vaultKey = angular.copy(vault_key);

View file

@ -0,0 +1,61 @@
/**
* Nextcloud - passman
*
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
(function () {
'use strict';
/**
* @ngdoc function
* @name passmanApp.controller:MainCtrl
* @description
* # MainCtrl
* Controller of the passmanApp
*/
angular.module('passmanApp')
.controller('RequestDeleteCtrl', ['$scope', '$location', '$http', '$routeParams', 'VaultService', 'NotificationService', '$translate',
function ($scope, $location, $http, $routeParams, VaultService, NotificationService, $translate) {
$scope.reason = '';
VaultService.getVault({guid: $routeParams.vault_id}).then(function(vault){
$scope.pending_deletion = vault.delete_request_pending;
});
$scope.requestDeletion = function () {
var queryUrl = OC.generateUrl('apps/passman/admin/request-deletion/'+ $routeParams.vault_id);
var params = {
reason: $scope.reason
};
$http.post(queryUrl, params).then(function (response) {
NotificationService.showNotification($translate.instant('deletion.requested'), 5000);
$location.path('#/');
});
};
$scope.removeRequestDeletion = function () {
var queryUrl = OC.generateUrl('apps/passman/admin/request-deletion/' + $routeParams.vault_id);
$http.delete(queryUrl).then(function (response) {
NotificationService.showNotification($translate.instant('deletion.removed'), 5000);
$location.path('#/');
});
};
}]);
}());

View file

@ -39,7 +39,6 @@
},
link: function (scope, element, attrs) {
console.log(attrs.showLabel)
scope.showLabel = (attrs.hasOwnProperty('showLabel'));
}
};

View file

@ -59,7 +59,7 @@ $(document).ready(function () {
setAdminKey: function (key, value) {
var request = $.ajax({
url: this._baseUrl + '/' + key + '/' + value +'/admin1/admin2',
url: this._baseUrl + '/' + key + '/' + value + '/admin1/admin2',
method: 'POST'
});
request.done(function () {
@ -72,7 +72,7 @@ $(document).ready(function () {
});
},
getKey: function (key) {
if(this._settings.hasOwnProperty(key)){
if (this._settings.hasOwnProperty(key)) {
return this._settings[key];
}
return false;
@ -124,7 +124,7 @@ $(document).ready(function () {
settings.setAdminKey('vault_key_strength', $(this).val());
});
if($('form[name="passman_settings"]').length === 2){
if ($('form[name="passman_settings"]').length === 2) {
$('form[name="passman_settings"]')[1].remove();
}
@ -132,10 +132,10 @@ $(document).ready(function () {
'source_account': '',
'destination_account': ''
};
$( ".username-autocomplete" ).autocomplete({
$(".username-autocomplete").autocomplete({
source: OC.generateUrl('apps/passman/admin/search'),
minLength: 1,
select: function( event, ui ) {
select: function (event, ui) {
accountMover[$(this).attr('id')] = ui.item.value;
}
});
@ -144,10 +144,10 @@ $(document).ready(function () {
var self = this;
$('#moveStatus').hide();
$(self).attr('disabled', 'disabled');
$(self).html('<i class="fa fa-spinner fa-spin"></i> Moving...');
if(accountMover.source_account && accountMover.destination_account){
$(self).html('<i class="fa fa-spinner fa-spin"></i> ' + OC.L10N.translate('passman', 'Moving') + '...');
if (accountMover.source_account && accountMover.destination_account) {
$.post(OC.generateUrl('apps/passman/admin/move'), accountMover, function (data) {
if(data.success){
if (data.success) {
$(self).removeAttr('disabled');
$(self).html('Move');
$('#moveStatus').fadeIn();
@ -159,5 +159,74 @@ $(document).ready(function () {
}
});
function format_date(date) {
date = new Date(date);
var month=date.getMonth();
var year=date.getFullYear();
var day=date.getDate();
var hour=date.getHours();
var minutes=date.getMinutes();
var seconds=date.getSeconds();
month=month+1; //javascript date goes from 0 to 11
if (month<10){
month="0"+month; //adding the prefix
}
if (hour<10){
hour="0"+hour; //adding the prefix
}
if (minutes<10){
minutes="0"+minutes; //adding the prefix
}
if (seconds<10){
seconds="0"+seconds; //adding the prefix
}
return day+"-"+month+"-"+year+" "+hour+":"+minutes+":"+seconds;
}
function acceptDeleteRequest (el, req) {
if (!confirm(OC.L10N.translate('passman', "Are you really sure?\nThis will delete the vault and all credentials in it!"))) {
return;
}
$.post(OC.generateUrl('apps/passman/admin/accept-delete-request'), req, function (result) {
console.log(result);
$(el).parent().parent().remove();
})
}
function ignoreDeleteRequest (el, req) {
$.ajax({
url: OC.generateUrl('apps/passman/admin/request-deletion/' + req.vault_guid),
type: 'DELETE',
success: function (result) {
$(el).parent().parent().remove();
}
});
}
$.get(OC.generateUrl('apps/passman/admin/delete-requests'), function (requests) {
var table = $('#requests-table tbody');
$.each(requests, function (k, request) {
var accept = $('<span class="link">[Accept]&nbsp;</span>');
accept.click(function () {
var _self = this;
acceptDeleteRequest(_self, request);
});
var ignore = $('<span class="link">[Ignore]</span>');
ignore.click(function () {
var _self = this;
ignoreDeleteRequest(_self, request);
});
var cols = $('<td>' + request.id + '</td><td>' + request.displayName + '</td><td>' + request.reason + '</td><td>' + format_date(request.created * 1000 )+ '</td>');
var actions = $('<td></td>').append(accept).append(ignore);
table.append($('<tr></tr>').append(cols).append(actions));
})
});
$('#passman-tabs').tabs();
});

View file

@ -1,4 +1,4 @@
angular.module('templates-main', ['views/credential_revisions.html', 'views/edit_credential.html', 'views/partials/credential_template.html', 'views/partials/forms/edit_credential/basics.html', 'views/partials/forms/edit_credential/custom_fields.html', 'views/partials/forms/edit_credential/files.html', 'views/partials/forms/edit_credential/otp.html', 'views/partials/forms/edit_credential/password.html', 'views/partials/forms/settings/export.html', 'views/partials/forms/settings/general_settings.html', 'views/partials/forms/settings/generic_csv_import.html', 'views/partials/forms/settings/import.html', 'views/partials/forms/settings/password_settings.html', 'views/partials/forms/settings/sharing.html', 'views/partials/forms/settings/tool.html', 'views/partials/forms/share_credential/basics.html', 'views/partials/forms/share_credential/link_sharing.html', 'views/partials/password-meter.html', 'views/settings.html', 'views/share_credential.html', 'views/show_vault.html', 'views/vaults.html']);
angular.module('templates-main', ['views/credential_revisions.html', 'views/edit_credential.html', 'views/partials/credential_template.html', 'views/partials/forms/edit_credential/basics.html', 'views/partials/forms/edit_credential/custom_fields.html', 'views/partials/forms/edit_credential/files.html', 'views/partials/forms/edit_credential/otp.html', 'views/partials/forms/edit_credential/password.html', 'views/partials/forms/settings/export.html', 'views/partials/forms/settings/general_settings.html', 'views/partials/forms/settings/generic_csv_import.html', 'views/partials/forms/settings/import.html', 'views/partials/forms/settings/password_settings.html', 'views/partials/forms/settings/sharing.html', 'views/partials/forms/settings/tool.html', 'views/partials/forms/share_credential/basics.html', 'views/partials/forms/share_credential/link_sharing.html', 'views/partials/password-meter.html', 'views/settings.html', 'views/share_credential.html', 'views/show_vault.html', 'views/vault_req_deletion.html', 'views/vaults.html']);
angular.module('views/credential_revisions.html', []).run(['$templateCache', function ($templateCache) {
'use strict';
@ -129,8 +129,14 @@ angular.module('views/show_vault.html', []).run(['$templateCache', function ($te
' (settings.user_sharing_enabled === 1 || settings.user_sharing_enabled === \'1\' || settings.link_sharing_enabled === 1 || settings.link_sharing_enabled === \'1\')"><span class="fa fa-share"></span> {{ \'share\' | translate}}</button> <button class="button" ng-click="getRevisions(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && hasPermission(selectedCredential.acl.permissions, permissions.permissions.HISTORY)"><span class="fa fa-undo"></span> {{ \'revisions\' | translate}}</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="recoverCredential(selectedCredential) && hasPermission(selectedCredential.acl.permissions, permissions.permissions.WRITE)"><span class="fa fa-recycle"></span> {{\'recover\' | translate}}</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="destroyCredential(selectedCredential)"><span class="fa fa-bomb"></span> {{\'destroy\' | translate}}</button></div></div></div></div><div class="share_popup" style="display: none">{{ \'sharereq.title\' | translate}}<br>{{ \'sharereq.line1\' | translate}}<br>{{ \'sharereq.line2\' | translate}} {{active_vault.vault_id}}<table class="table"><thead><tr><td>{{ \'label\' | translate}}</td><td>{{ \'permissions\' | translate}}</td><td>{{ \'received.from\' | translate}}</td><td>{{ \'date\' | translate}}</td></tr></thead><tr ng-repeat="share_request in incoming_share_requests" ng-if="share_request.target_vault_id == active_vault.vault_id"><td>{{share_request.credential_label}}</td><td>{{share_request.permissions}}</td><td>{{share_request.from_user_id}}</td><td>{{share_request.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td><td><span class="link" ng-click="acceptShareRequest(share_request)">{{ \'accept\' | translate}}</span> | <span class="link" ng-click="declineShareRequest(share_request)">{{ \'decline\' | translate}}</span></td></tr></table></div>');
}]);
angular.module('views/vault_req_deletion.html', []).run(['$templateCache', function ($templateCache) {
'use strict';
$templateCache.put('views/vault_req_deletion.html',
'<div class="vault_wrapper"><div class="reset_form" ng-show="!pending_deletion">{{ \'req.intro1\' | translate }}<br>{{ \'req.intro2\' | translate }}<br>{{ \'req.intro3\' | translate }}<br><br><b>{{ \'request.deletion.warning\' | translate}}</b><label>{{ \'request.deletion.reason\' | translate }}</label><input type="text" ng-model="reason" class="form-control"> <button class="button button-red" ng-click="requestDeletion()">{{ \'request.deletion.accept\' | translate }}</button> <a class="pull-right button button-geen" ng-href="#/">{{ \'cancel\' | translate}}</a></div><div class="reset_form" ng-show="pending_deletion"><button class="button button-red" ng-click="removeRequestDeletion()">Cancel destruction request</button> <a class="pull-right button button-geen" ng-href="#/">Cancel</a></div></div>');
}]);
angular.module('views/vaults.html', []).run(['$templateCache', function ($templateCache) {
'use strict';
$templateCache.put('views/vaults.html',
'<div class="vault_wrapper"><div class="vaults" ng-if="!list_selected_vault && !creating_vault"><div class="ui-select-container ui-select-bootstrap vaultlist"><ul><li ng-click="newVault()">+ Create a new vault</li><li ng-repeat="vault in vaults" ng-class="{\'selected\': vault == list_selected_vault }" ng-click="selectVault(vault)"><div><span class="ui-select-choices-row-inner"><div class="ng-binding ng-scope">{{vault.name}}</div><small class="ng-binding ng-scope">{{ \'created\' | translate}}: {{vault.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}} | {{ \'last.access\' | translate}}: <span ng-if="vault.last_access > 0">{{vault.last_access * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span> <span ng-if="vault.last_access === 0">{{\'never\' | translate}}</span></small></span></div></li><li ng-if="vaults.length === 0">{{ \'no.vaults\' | translate}}</li></ul></div></div><div ng-if="creating_vault"><div class="login_form" ng-init="vault_name = \'\'; vault_key=\'\'; ">{{\'new.vault.name\' | translate}}<div><input type="text" ng-model="vault_name" required></div><div>{{ \'new.vault.pass\' | translate}} <input type="password" ng-model="vault_key" required><ng-password-meter password="vault_key" score="vault_key_score"></ng-password-meter></div><div>{{ \'new.vault.passr\' | translate}} <input type="password" ng-model="vault_key2" required></div><div ng-show="error || vault_key_score.score < minimal_value_key_strength" class="error"><ul><li ng-show="error">{{error}}</li><li ng-show="vault_key_score.score < minimal_value_key_strength">{{\'min.vault.key.strength\' | translate:required_score}}</li></ul></div><div><small>{{\'new.vault.sharing_key_notice\' | translate}}</small></div><div class="button_wrapper"><button class="button button-geen" ng-if="!creating_keys" ng-click="createVault(vault_name, vault_key, vault_key2)" ng-disabled="vault_key_score.score < minimal_value_key_strength || vault_key !== vault_key2 || vault_key === \'\'">{{ \'new.vault.create\' | translate }}</button><div class="button" ng-if="creating_keys"><span><i class="fa fa-spinner fa-spin"></i> {{creating_keys}}</span></div><div class="button button-red" ng-click="clearState()">{{ \'cancel\' | translate}}</div><div class="hidden">{{sharing_keys}}</div></div></div></div><div ng-if="list_selected_vault != false"><div class="vaultlist"><ul><li ng-click="clearState()">{{ \'go.back.vaults\' | translate }}</li></ul></div><div class="login_form"><div ng-show="error" class="error"><ul><li>{{error}}</li></ul></div>{{ \'input.vault.password\' | translate}} {{list_selected_vault.name}}<div class="pw-input"><input type="password" ng-model="vault_key" ng-enter="loginToVault(list_selected_vault, vault_key)"> <small class="last_access">{{\'last.access\' | translate}}: <span ng-if="list_selected_vault.last_access > 0">{{list_selected_vault.last_access * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span> <span ng-if="list_selected_vault.last_access === 0">Never</span></small></div><div class="login_opts"><div><label><input type="checkbox" ng-checked="default_vault" ng-click="toggleDefaultVault()"> {{ \'vault.default\' | translate}}</label></div><div><label><input type="checkbox" ng-checked="remember_vault_password" ng-click="toggleRememberPassword()"> {{ \'vault.auto.login\' | translate}}</label></div><div><label><input type="checkbox" ng-checked="auto_logout_timer" ng-click="toggleAutoLogout()"> {{ \'auto.logout\' | translate}}</label><select ng-model="logout_timer" ng-change="selectLogoutTimer(logout_timer)"><option value="0">Never</option><option value="30">30 minutes</option><option value="60">60 minutes</option><option value="90">90 minutes</option><option value="180">3 hour</option><option value="480">8 hour</option></select></div></div><div class="alert alert-danger" ng-show="vault_tries[list_selected_vault.guid].timeout !== 0" translate="vault.locked" translate-value-tries="{{ vault_tries[list_selected_vault.guid].tries }}" translate-value-time="{{ vault_tries[list_selected_vault.guid].timeout | toHHMMSS }}"></div><button class="button button-geen" ng-click="loginToVault(list_selected_vault, vault_key)" ng-disabled="vault_tries[list_selected_vault.guid].timeout !== 0">{{ \'vault.decrypt\' | translate}}</button></div></div></div>');
'<div class="vault_wrapper"><div class="vaults" ng-if="!list_selected_vault && !creating_vault"><div class="ui-select-container ui-select-bootstrap vaultlist"><ul><li ng-click="newVault()">+ Create a new vault</li><li ng-repeat="vault in vaults" ng-class="{\'selected\': vault == list_selected_vault }" ng-click="selectVault(vault)"><div><span class="ui-select-choices-row-inner"><div class="ng-binding ng-scope">{{vault.name}} <span class="pull-right" style="color: #ce3702" ng-show="vault.delete_request_pending">{{ \'delete.request.pending\' | translate}}</span></div><small class="ng-binding ng-scope">{{ \'created\' | translate}}: {{vault.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}} | {{ \'last.access\' | translate}}: <span ng-if="vault.last_access > 0">{{vault.last_access * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span> <span ng-if="vault.last_access === 0">{{\'never\' | translate}}</span></small></span></div></li><li ng-if="vaults.length === 0">{{ \'no.vaults\' | translate}}</li></ul></div></div><div ng-if="creating_vault"><div class="login_form" ng-init="vault_name = \'\'; vault_key=\'\'; ">{{\'new.vault.name\' | translate}}<div><input type="text" ng-model="vault_name" required></div><div>{{ \'new.vault.pass\' | translate}} <input type="password" ng-model="vault_key" required><ng-password-meter password="vault_key" score="vault_key_score"></ng-password-meter></div><div>{{ \'new.vault.passr\' | translate}} <input type="password" ng-model="vault_key2" required></div><div ng-show="error || vault_key_score.score < minimal_value_key_strength" class="error"><ul><li ng-show="error">{{error}}</li><li ng-show="vault_key_score.score < minimal_value_key_strength">{{\'min.vault.key.strength\' | translate:required_score}}</li></ul></div><div><small>{{\'new.vault.sharing_key_notice\' | translate}}</small></div><div class="button_wrapper"><button class="button button-geen" ng-if="!creating_keys" ng-click="createVault(vault_name, vault_key, vault_key2)" ng-disabled="vault_key_score.score < minimal_value_key_strength || vault_key !== vault_key2 || vault_key === \'\'">{{ \'new.vault.create\' | translate }}</button><div class="button" ng-if="creating_keys"><span><i class="fa fa-spinner fa-spin"></i> {{creating_keys}}</span></div><div class="button button-red" ng-click="clearState()">{{ \'cancel\' | translate}}</div><div class="hidden">{{sharing_keys}}</div></div></div></div><div ng-if="list_selected_vault != false"><div class="vaultlist"><ul><li ng-click="clearState()">{{ \'go.back.vaults\' | translate }}</li></ul></div><div class="login_form"><div ng-show="error" class="error"><ul><li>{{error}}</li></ul></div>{{ \'input.vault.password\' | translate}} {{list_selected_vault.name}}<div class="pw-input"><input type="password" ng-model="vault_key" ng-enter="loginToVault(list_selected_vault, vault_key)"> <small class="last_access">{{\'last.access\' | translate}}: <span ng-if="list_selected_vault.last_access > 0">{{list_selected_vault.last_access * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span> <span ng-if="list_selected_vault.last_access === 0">Never</span></small></div><div class="login_opts"><div><label><input type="checkbox" ng-checked="default_vault" ng-click="toggleDefaultVault()"> {{ \'vault.default\' | translate}}</label></div><div><label><input type="checkbox" ng-checked="remember_vault_password" ng-click="toggleRememberPassword()"> {{ \'vault.auto.login\' | translate}}</label></div><div><label><input type="checkbox" ng-checked="auto_logout_timer" ng-click="toggleAutoLogout()"> {{ \'auto.logout\' | translate}}</label><select ng-model="logout_timer" ng-change="selectLogoutTimer(logout_timer)"><option value="0">Never</option><option value="30">30 minutes</option><option value="60">60 minutes</option><option value="90">90 minutes</option><option value="180">3 hour</option><option value="480">8 hour</option></select></div></div><div class="alert alert-danger" ng-show="vault_tries[list_selected_vault.guid].timeout !== 0" translate="vault.locked" translate-value-tries="{{ vault_tries[list_selected_vault.guid].tries }}" translate-value-time="{{ vault_tries[list_selected_vault.guid].timeout | toHHMMSS }}"></div><button class="button button-geen" ng-click="loginToVault(list_selected_vault, vault_key)" ng-disabled="vault_tries[list_selected_vault.guid].timeout !== 0">{{ \'vault.decrypt\' | translate}}</button> <span ng-click="forgottenPassword = true;" style="margin-top: 10px; padding: 6px 12px" class="link pull-right" ng-show="!forgottenPassword">Forgot password?</span> <button ng-show="forgottenPassword" class="pull-right button button-red" ng-click="requestDeletion(list_selected_vault)"><span ng-show="list_selected_vault.delete_request_pending">{{ \'cancel.request.deletion\' | translate }}</span> <span ng-show="!list_selected_vault.delete_request_pending">{{ \'request.deletion\' | translate }}</span></button></div></div></div>');
}]);

View file

@ -0,0 +1,67 @@
<?php
/**
* Nextcloud - passman
*
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Passman\Db;
use \OCP\AppFramework\Db\Entity;
/**
* @method integer getId()
* @method void setId(integer $value)
* @method void setVaultGuid(string $value)
* @method string getVaultGuid()
* @method void setReason(string $value)
* @method string getReason()
* @method void setRequestedBy(string $value)
* @method string getRequestedBy()
* @method void setCreated(integer $value)
* @method integer getCreated()
*/
class DeleteVaultRequest extends Entity implements \JsonSerializable{
use EntityJSONSerializer;
protected $vaultGuid;
protected $reason;
protected $requestedBy;
protected $created;
public function __construct() {
// add types in constructor
$this->addType('id', 'integer');
$this->addType('created', 'integer');
}
/**
* Turns entity attributes into an array
*/
public function jsonSerialize() {
return [
'id' => $this->getId(),
'vault_guid' => $this->getVaultGuid(),
'reason' => $this->getReason(),
'requested_by' => $this->getRequestedBy(),
'created' => $this->getCreated(),
];
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* Nextcloud - passman
*
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Passman\Db;
use Icewind\SMB\Share;
use OCA\Passman\Utility\Utils;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Mapper;
use OCP\IDBConnection;
class DeleteVaultRequestMapper extends Mapper {
const TABLE_NAME = 'passman_delete_vault_request';
public function __construct(IDBConnection $db) {
parent::__construct($db, self::TABLE_NAME);
}
/**
* Create a new enty in the db
* @param DeleteVaultRequest $request
* @return \OCP\AppFramework\Db\Entity
*/
public function createRequest(DeleteVaultRequest $request){
return $this->insert($request);
}
/**
* Get all delete requests
* @return \OCP\AppFramework\Db\Entity
*/
public function getDeleteRequests(){
$q = "SELECT * FROM *PREFIX*" . self::TABLE_NAME;
return $this->findEntities($q);
}
/**
* Get request for an vault id
* @param $vault_id integer The vault id
* @return \OCP\AppFramework\Db\Entity
*/
public function getDeleteRequestsForVault($vault_guid){
$q = "SELECT * FROM *PREFIX*" . self::TABLE_NAME .' WHERE `vault_guid` = ?';
return $this->findEntity($q, [$vault_guid]);
}
/**
* Deletes the given delete request
* @param DeleteVaultRequest $shareRequest Request to delete
* @return DeleteVaultRequest The deleted request
*/
public function removeDeleteVaultRequest(DeleteVaultRequest $request){
return $this->delete($request);
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* Nextcloud - passman
*
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Passman\Service;
use OCA\Passman\Db\DeleteVaultRequest;
use OCA\Passman\Db\DeleteVaultRequestMapper;
use OCP\AppFramework\Db\DoesNotExistException;
class DeleteVaultRequestService {
private $deleteVaultRequestMapper;
public function __construct(DeleteVaultRequestMapper $deleteVaultRequestMapper) {
$this->deleteVaultRequestMapper = $deleteVaultRequestMapper;
}
/**
* Create a new DeleteVaultRequest
*
* @param $request DeleteVaultRequest
* @return \OCA\Passman\Db\DeleteVaultRequest
*/
public function createRequest(DeleteVaultRequest $request) {
return $this->deleteVaultRequestMapper->insert($request);
}
/**
* Create a new DeleteVaultRequest
*
* @return \OCA\Passman\Db\DeleteVaultRequest[]
*/
public function getDeleteRequests() {
return $this->deleteVaultRequestMapper->getDeleteRequests();
}
/**
* Create a new DeleteVaultRequest
*
* @param $vault_id integer The vault id
* @return bool | DeleteVaultRequest
*/
public function getDeleteRequestForVault($vault_guid) {
try {
$result = $this->deleteVaultRequestMapper->getDeleteRequestsForVault($vault_guid);
return $result;
} catch (\Exception $e) {
return false;
}
}
/**
* Create a new DeleteVaultRequest
*
* @param $req DeleteVaultRequest
* @return bool | DeleteVaultRequest
*/
public function removeDeleteRequestForVault(DeleteVaultRequest $req) {
$this->deleteVaultRequestMapper->removeDeleteVaultRequest($req);
}
}

View file

@ -50,4 +50,14 @@ class Utils {
return sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535));
}
/**
* @param $uid
* @return string
*/
public static function getNameByUid($uid){
$um = \OC::$server->getUserManager();
$u = $um->get($uid);
return $u->getDisplayName();
}
}

View file

@ -22,6 +22,7 @@
"grunt-replace": "^1.0.1",
"html-minifier": "^3.0.2",
"jasmine-core": "^2.5.2",
"jshint-stylish": "^2.2.1",
"karma": "^1.3.0",
"karma-chrome-launcher": "^2.0.0",
"karma-firefox-launcher": "^1.0.0",

View file

@ -9,4 +9,16 @@
width: 350px;
}
}
#requests-table{
width: 100%;
}
.link{
color: rgb(0, 102, 255) !important;
cursor: pointer;
}
.link:hover{
text-decoration: underline;
}
}

View file

@ -90,8 +90,20 @@
margin-top: 10px;
}
}
.reset_form {
padding: 16px;
label{
margin-top: 20px;
display: block;
}
input[type="text"]{
width: 100%;
}
}
.login_opts{
margin-bottom: 10px;
}
}

View file

@ -46,6 +46,7 @@ script('passman', 'app/controllers/settings');
script('passman', 'app/controllers/import');
script('passman', 'app/controllers/export');
script('passman', 'app/controllers/generic-csv-importer');
script('passman', 'app/controllers/vaultreqdeletion');
script('passman', 'app/filters/range');
script('passman', 'app/filters/propsfilter');
script('passman', 'app/filters/byte');

View file

@ -11,10 +11,10 @@ style('passman', 'vendor/font-awesome/font-awesome.min');
$checkVersion = OC::$server->getConfig()->getAppValue('passman', 'check_version', '1') === '1';
$AppInstance = new App();
$localVersion = $AppInstance->getAppInfo("passman")["version"];
$githubVersion = $l->t('Unable to get version info');
if ($checkVersion) {
// get latest master version
$version = false;
$githubVersion = $l->t('Unable to get version info');
$url = 'https://raw.githubusercontent.com/nextcloud/passman/master/appinfo/info.xml';
try {
@ -68,7 +68,7 @@ $ciphers = openssl_get_cipher_methods();
<a href="#mover"><?php p($l->t('Credential mover')); ?></a>
</li>
<li>
<a href="#tabs-3"><?php p($l->t('Vault delete requests')); ?></a>
<a href="#tabs-3"><?php p($l->t('Vault destruction requests')); ?></a>
</li>
</ul>
<div id="general">
@ -162,7 +162,21 @@ $ciphers = openssl_get_cipher_methods();
</div>
<div id="tabs-3">
Requests to delete vault
<?php p($l->t('Requests to destroy vault')); ?>
<table id="requests-table">
<thead>
<tr>
<th><?php p($l->t('Request ID')); ?></th>
<th><?php p($l->t('Requested by')); ?></th>
<th><?php p($l->t('Reason')); ?></th>
<th><?php p($l->t('Created')); ?></th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>

View file

@ -0,0 +1,30 @@
<div class="vault_wrapper">
<div class="reset_form" ng-show="!pending_deletion">
{{ 'req.intro1' | translate }}<br/>
{{ 'req.intro2' | translate }}<br/>
{{ 'req.intro3' | translate }}<br/>
<br/>
<b>{{ 'request.deletion.warning' | translate}}</b>
<label>{{ 'request.deletion.reason' | translate }}</label>
<input type="text" ng-model="reason" class="form-control">
<button class="button button-red"
ng-click="requestDeletion()">
{{ 'request.deletion.accept' | translate }}
</button>
<a class="pull-right button button-geen" ng-href="#/">
{{ 'cancel' | translate}}
</a>
</div>
<div class="reset_form" ng-show="pending_deletion">
<button class="button button-red"
ng-click="removeRequestDeletion()">
Cancel destruction request
</button>
<a class="pull-right button button-geen" ng-href="#/">
Cancel
</a>
</div>
</div>

View file

@ -9,7 +9,7 @@
ng-click="selectVault(vault)">
<div>
<span class="ui-select-choices-row-inner">
<div class="ng-binding ng-scope">{{vault.name}}</div>
<div class="ng-binding ng-scope">{{vault.name}} <span class="pull-right" style="color: #ce3702;" ng-show="vault.delete_request_pending">{{ 'delete.request.pending' | translate}}</span></div>
<small class="ng-binding ng-scope">
{{ 'created' | translate}}: {{vault.created * 1000 | date:'dd-MM-yyyy @ HH:mm:ss'}}
@ -134,6 +134,11 @@
ng-click="loginToVault(list_selected_vault, vault_key)" ng-disabled="vault_tries[list_selected_vault.guid].timeout !== 0">
{{ 'vault.decrypt' | translate}}
</button>
<span ng-click="forgottenPassword = true;" style="margin-top: 10px; padding: 6px 12px;" class="link pull-right" ng-show="!forgottenPassword">Forgot password?</span>
<button ng-show="forgottenPassword" class="pull-right button button-red" ng-click="requestDeletion(list_selected_vault)">
<span ng-show="list_selected_vault.delete_request_pending">{{ 'cancel.request.deletion' | translate }}</span>
<span ng-show="!list_selected_vault.delete_request_pending">{{ 'request.deletion' | translate }}</span>
</button>
</div>
</div>