From 67734860595060e40633d555dbc879da66ec2879 Mon Sep 17 00:00:00 2001 From: brantje Date: Sun, 11 Sep 2016 19:45:20 +0200 Subject: [PATCH] Starting to work on login to vault --- appinfo/database.xml | 157 ++++++++++++++++++++++++++- appinfo/info.xml | 2 +- appinfo/routes.php | 26 ++--- controller/credentialcontroller.php | 51 +++++++-- controller/vaultcontroller.php | 3 +- css/app.css | 4 + css/app.css.map | 2 +- js/app/controllers/vault.js | 53 +++++++-- js/app/services/credentialservice.js | 65 ++++++++++- js/app/services/encryptservice.js | 44 ++++++++ js/app/services/vaultservice.js | 17 ++- js/script.js | 0 js/templates.js | 2 +- js/vendor/sjcl/sjcl.js | 41 +++++++ lib/Db/Credential.php | 127 +++++++++++++++++++--- lib/Db/CredentialMapper.php | 68 ++++++++++++ lib/Db/Vault.php | 13 +-- lib/Service/CredentialService.php | 31 ++++++ sass/vaults.scss | 6 + templates/main.php | 2 + templates/views/vaults.html | 16 ++- 21 files changed, 655 insertions(+), 75 deletions(-) create mode 100644 js/app/services/encryptservice.js delete mode 100644 js/script.js create mode 100644 js/vendor/sjcl/sjcl.js create mode 100644 lib/Db/CredentialMapper.php create mode 100644 lib/Service/CredentialService.php diff --git a/appinfo/database.xml b/appinfo/database.xml index 29b75449..fce99ff8 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -55,7 +55,7 @@ - passman_last_access_index + passman_vault_last_access_index last_access @@ -83,4 +83,159 @@ + + *dbprefix*passman_credentials + + + id + integer + true + true + true + true + 8 + + + guid + text + false + 64 + + + + user_id + text + false + 64 + + + vault_id + text + true + 64 + + + + label + clob + true + + + + description + clob + false + + + + created + integer + 8 + 0 + false + true + + + changed + integer + 0 + 8 + false + true + + + tags + clob + false + + + email + clob + false + + + username + clob + false + + + password + clob + false + + + url + clob + false + + + favicon + clob + false + + + renew_interval + integer + 0 + 8 + false + true + + + expire_time + integer + 0 + 8 + false + true + + + delete_time + integer + 0 + 8 + false + true + + + files + clob + false + + + + custom_fields + clob + false + + + otp + clob + false + + + hidden + boolean + false + + + passman_credential_id_index + + id + + + + passman_credential_vault_id_index + + vault_id + + + + passman_credential_user_id_index + + user_id + + + +
\ No newline at end of file diff --git a/appinfo/info.xml b/appinfo/info.xml index 39d70b08..8e5ebd2c 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ A password manager for Nextcloud AGPL Sander Brand - 1.0.2.1 + 1.0.2.2 Passman other https://github.com/nextcloud/passman/ diff --git a/appinfo/routes.php b/appinfo/routes.php index 3e7df09a..e5c2abd4 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -22,25 +22,25 @@ return [ ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'], //Vault - ['name' => 'vault#listVaults', 'url' => '/api/v1/vaults', 'verb' => 'GET'], - ['name' => 'vault#create', 'url' => '/api/v1/vaults', 'verb' => 'POST'], - ['name' => 'vault#get', 'url' => '/api/v1/vaults/{vault_id}', 'verb' => 'GET'], - ['name' => 'vault#update', 'url' => '/api/v1/vaults/{vault_id}', 'verb' => 'PATCH'], - ['name' => 'vault#delete', 'url' => '/api/v1/vaults/{vault_id}', 'verb' => 'DELETE'], + ['name' => 'vault#listVaults', 'url' => '/api/v2/vaults', 'verb' => 'GET'], + ['name' => 'vault#create', 'url' => '/api/v2/vaults', 'verb' => 'POST'], + ['name' => 'vault#get', 'url' => '/api/v2/vaults/{vault_id}', 'verb' => 'GET'], + ['name' => 'vault#update', 'url' => '/api/v2/vaults/{vault_id}', 'verb' => 'PATCH'], + ['name' => 'vault#delete', 'url' => '/api/v2/vaults/{vault_id}', 'verb' => 'DELETE'], //Credential - ['name' => 'credential#createCredential', 'url' => '/api/v1/credentials', 'verb' => 'POST'], - ['name' => 'credential#getCredential', 'url' => '/api/v1/credentials/{credential_id}', 'verb' => 'GET'], - ['name' => 'credential#updateCredential', 'url' => '/api/v1/credentials/{credential_id}', 'verb' => 'PATCH'], - ['name' => 'credential#deleteCredential', 'url' => '/api/v1/credentials/{credential_id}', 'verb' => 'DELETE'], + ['name' => 'credential#createCredential', 'url' => '/api/v2/credentials', 'verb' => 'POST'], + ['name' => 'credential#getCredential', 'url' => '/api/v2/credentials/{credential_id}', 'verb' => 'GET'], + ['name' => 'credential#updateCredential', 'url' => '/api/v2/credentials/{credential_id}', 'verb' => 'PATCH'], + ['name' => 'credential#deleteCredential', 'url' => '/api/v2/credentials/{credential_id}', 'verb' => 'DELETE'], //Revisions - ['name' => 'credential#getRevision', 'url' => '/api/v1/credentials/{credential_id}/revision', 'verb' => 'GET'], - ['name' => 'credential#deleteRevision', 'url' => '/api/v1/credentials/{credential_id}/revision/{revision_id}', 'verb' => 'DELETE'], + ['name' => 'credential#getRevision', 'url' => '/api/v2/credentials/{credential_id}/revision', 'verb' => 'GET'], + ['name' => 'credential#deleteRevision', 'url' => '/api/v2/credentials/{credential_id}/revision/{revision_id}', 'verb' => 'DELETE'], //File stuff - ['name' => 'credential#uploadFile', 'url' => '/api/v1/credentials/{credential_id}/file', 'verb' => 'POST'], - ['name' => 'credential#deleteFile', 'url' => '/api/v1/credentials/{credential_id}/file/{file_id}', 'verb' => 'DELETE'], + ['name' => 'credential#uploadFile', 'url' => '/api/v2/credentials/{credential_id}/file', 'verb' => 'POST'], + ['name' => 'credential#deleteFile', 'url' => '/api/v2/credentials/{credential_id}/file/{file_id}', 'verb' => 'DELETE'], ] ]; \ No newline at end of file diff --git a/controller/credentialcontroller.php b/controller/credentialcontroller.php index bb9ea218..37f5f74c 100644 --- a/controller/credentialcontroller.php +++ b/controller/credentialcontroller.php @@ -12,28 +12,57 @@ namespace OCA\Passman\Controller; use OCP\IRequest; -use OCA\Passman\Credential; - -use OCP\AppFramework\Http\TemplateResponse; -use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\ApiController; -use OCP\AppFramework\Controller; - -class CredentialController extends Controller { +use OCA\Passman\Service\CredentialService; +class CredentialController extends ApiController { private $userId; - - public function __construct($AppName, IRequest $request, $UserId){ + private $credentialService; + public function __construct($AppName, + IRequest $request, + $UserId, + CredentialService $credentialService){ parent::__construct($AppName, $request); $this->userId = $UserId; + $this->credentialService = $credentialService; } /** * @NoAdminRequired */ - public function createCredential() { - return; + public function createCredential($changed, $created, + $credential_id, $custom_fields, $delete_time, + $description, $email, $expire_time, $favicon, $files, $guid, + $hidden, $label, $otp, $password, $renew_interval, + $tags, $url, $username, $vault_id) { + $credential = array( + 'credential_id' => $credential_id, + 'guid' => $guid, + 'user_id' => $this->userId, + 'vault_id' => $vault_id, + 'label' => $label, + 'description' => $description, + 'created' => $created, + 'changed' => $changed, + 'tags' => $tags, + 'email' => $email, + 'username' => $username, + 'password' => $password, + 'url' => $url, + 'favicon' => $favicon, + 'renew_interval' => $renew_interval, + 'expire_time' => $expire_time, + 'delete_time' => $delete_time, + 'files' => $files, + 'custom_fields' => $custom_fields, + 'otp' => $otp, + 'hidden' => $hidden, + + ); + $credential = $this->credentialService->createCredential($credential); + return new JSONResponse($credential); } /** diff --git a/controller/vaultcontroller.php b/controller/vaultcontroller.php index 61023887..3f2cfc66 100644 --- a/controller/vaultcontroller.php +++ b/controller/vaultcontroller.php @@ -12,10 +12,11 @@ namespace OCA\Passman\Controller; use OCP\IRequest; -use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\ApiController; use OCA\Passman\Service\VaultService; + + class VaultController extends ApiController { private $userId; private $vaultService; diff --git a/css/app.css b/css/app.css index a2755964..8ae5829a 100644 --- a/css/app.css +++ b/css/app.css @@ -54,4 +54,8 @@ .vault_wrapper .login_form .button { margin-top: 10px; } +@media screen and (max-width: 768px) { + .vault_wrapper { + width: 90%; } } + /*# sourceMappingURL=app.css.map */ diff --git a/css/app.css.map b/css/app.css.map index 9e7366d8..e8b55edc 100644 --- a/css/app.css.map +++ b/css/app.css.map @@ -1,6 +1,6 @@ { "version": 3, -"mappings": "AAAA,YAAY;EACV,UAAU,EAAE,OAAO;EACnB,KAAK,EAAE,IAAI;;AAEb,kBAAkB;EAChB,UAAU,EAAE,OAAoB;EAChC,KAAK,EAAE,IAAI;;AAGb,WAAW;EACT,UAAU,EAAE,OAAO;EACnB,KAAK,EAAE,IAAI;;AAEb,iBAAiB;EACf,UAAU,EAAE,OAAoB;EAChC,KAAK,EAAE,IAAI;;ACdb,cAAc;EACZ,MAAM,EAAE,MAAM;EACd,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,KAAK;ECMhB,qBAAqB,EDLE,GAAG;ECM1B,aAAa,EDNU,GAAG;ECO1B,eAAe,EAAE,WAAW;EAAG,qDAAqD;EDNpF,UAAU,EAAE,cAAc;EAC1B,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,KAAK;EACd,yBAAU;IAER,OAAO,EAAE,GAAG;IACZ,4HAAiB;MACf,MAAM,EAAE,OAAO;IAEjB,+BAAK;MACH,KAAK,EAAE,OAAO;IAEhB,qCAAW;MACT,gBAAgB,EAAE,kBAAkB;MACpC,KAAK,EAAE,IAAI;IAEb,4BAAE;MACA,aAAa,EAAE,iBAAiB;MAChC,OAAO,EAAE,IAAI;IAEf,kCAAQ;MACN,gBAAgB,EAAE,OAAO;EAG7B,0BAAW;IACT,OAAO,EAAE,IAAI;IACb,gGAA0C;MACxC,KAAK,EAAE,IAAI;MCvBf,qBAAqB,EDwBM,GAAG;MCvB9B,aAAa,EDuBc,GAAG;MCtB9B,eAAe,EAAE,WAAW;MAAG,qDAAqD;IDyBhF,kDAAO;MACL,KAAK,EAAE,GAAG;MACV,OAAO,EAAE,YAAY;IAGzB,kCAAO;MACL,UAAU,EAAE,IAAI", +"mappings": "AAAA,YAAY;EACV,UAAU,EAAE,OAAO;EACnB,KAAK,EAAE,IAAI;;AAEb,kBAAkB;EAChB,UAAU,EAAE,OAAoB;EAChC,KAAK,EAAE,IAAI;;AAGb,WAAW;EACT,UAAU,EAAE,OAAO;EACnB,KAAK,EAAE,IAAI;;AAEb,iBAAiB;EACf,UAAU,EAAE,OAAoB;EAChC,KAAK,EAAE,IAAI;;ACdb,cAAc;EACZ,MAAM,EAAE,MAAM;EACd,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,KAAK;ECMhB,qBAAqB,EDLE,GAAG;ECM1B,aAAa,EDNU,GAAG;ECO1B,eAAe,EAAE,WAAW;EAAG,qDAAqD;EDNpF,UAAU,EAAE,cAAc;EAC1B,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,KAAK;EACd,yBAAU;IAER,OAAO,EAAE,GAAG;IACZ,4HAAiB;MACf,MAAM,EAAE,OAAO;IAEjB,+BAAK;MACH,KAAK,EAAE,OAAO;IAEhB,qCAAW;MACT,gBAAgB,EAAE,kBAAkB;MACpC,KAAK,EAAE,IAAI;IAEb,4BAAE;MACA,aAAa,EAAE,iBAAiB;MAChC,OAAO,EAAE,IAAI;IAEf,kCAAQ;MACN,gBAAgB,EAAE,OAAO;EAG7B,0BAAW;IACT,OAAO,EAAE,IAAI;IACb,gGAA0C;MACxC,KAAK,EAAE,IAAI;MCvBf,qBAAqB,EDwBM,GAAG;MCvB9B,aAAa,EDuBc,GAAG;MCtB9B,eAAe,EAAE,WAAW;MAAG,qDAAqD;IDyBhF,kDAAO;MACL,KAAK,EAAE,GAAG;MACV,OAAO,EAAE,YAAY;IAGzB,kCAAO;MACL,UAAU,EAAE,IAAI;;AAKtB,oCAAoC;EAClC,cAAc;IACZ,KAAK,EAAE,GAAG", "sources": ["../sass/partials/button.scss","../sass/vaults.scss","../sass/mixins.scss"], "names": [], "file": "app.css" diff --git a/js/app/controllers/vault.js b/js/app/controllers/vault.js index b85cb1f9..a4d63bd1 100644 --- a/js/app/controllers/vault.js +++ b/js/app/controllers/vault.js @@ -8,18 +8,24 @@ * Controller of the passmanApp */ angular.module('passmanApp') - .controller('VaultCtrl', ['$scope', 'VaultService', 'SettingsService', function ($scope, VaultService, SettingsService) { + .controller('VaultCtrl', ['$scope', 'VaultService', 'SettingsService', 'CredentialService', function ($scope, VaultService, SettingsService, CredentialService) { VaultService.getVaults().then(function (vaults) { $scope.vaults = vaults; if(SettingsService.getSetting('defaultVault') != null){ var default_vault = SettingsService.getSetting('defaultVault'); - angular.forEach(vaults, function (vault) { + + /** + * Using a native for loop for preformance reasons. + * More info see http://stackoverflow.com/questions/13843972/angular-js-break-foreach + */ + for(var i = 0; i < vaults.length; i++){ + var vault = vaults[i]; if(vault.guid == default_vault.guid){ $scope.default_vault = true; $scope.list_selected_vault = SettingsService.getSetting('defaultVault'); - return; + break; } - }) + } } }); @@ -50,9 +56,42 @@ angular.module('passmanApp') $scope.creating_vault = true; }; - $scope.createVault = function(vault_name){ - VaultService.createVault(vault_name).then(function (result) { - console.log(result) + + $scope.vaultDecryptionKey = ''; + $scope.loginToVault = function (vault) { + VaultService.getVault(vault).then(function(credentials){ + for(var i = 0; i < credentials.length; i++){ + var credential = credentials[i]; + if(credential.hidden = true){ + console.log(credential); + break; + } + } }) }; + + $scope.vaultKey = ''; + $scope.vaultKey_2 = ''; + $scope.createVault = function(vault_name){ + if($scope.vaultKey != $scope.vaultKey_2){ + //@todo Show an message + return; + } + VaultService.createVault(vault_name).then(function (vault) { + $scope.vaults.push(vault) + var _vault = angular.copy(vault) + _vault.vaultKey = angular.copy($scope.vaultKey); + VaultService.setActiveVault(_vault); + var test_credential = CredentialService.newCredential(); + test_credential.label = 'Test key for vault '+ vault_name; + test_credential.hidden = true; + test_credential.vault_id = vault.vault_id; + test_credential.password = 'lorum ipsum'; + CredentialService.createCredential(test_credential).then(function (result) { + console.log('succes =)') + console.log(result) + //@TODO Redirect to newly created vault + }) + }); + }; }]); diff --git a/js/app/services/credentialservice.js b/js/app/services/credentialservice.js index abbba1f8..ab0f19f4 100644 --- a/js/app/services/credentialservice.js +++ b/js/app/services/credentialservice.js @@ -8,6 +8,65 @@ * Service in the passmanApp. */ angular.module('passmanApp') - .service('CredentialService', function () { - // AngularJS will instantiate a singleton by calling "new" on this function - }); + .service('CredentialService', ['$http', 'EncryptService', function ($http, EncryptService) { + var credential = { + 'credential_id': null, + 'guid': null, + 'vault_id': null, + 'label': null, + 'description': null, + 'created': null, + 'changed': null, + 'tags': null, + 'email': null, + 'username': null, + 'password': null, + 'url': null, + 'favicon': null, + 'renew_interval': null, + 'expire_time': null, + 'delete_time': null, + 'files': null, + 'custom_fields': null, + 'otp': null, + 'hidden': false + }; + var _encryptedFields = ['description','username','password','files','custom_fields','otp']; + return { + newCredential: function () { + return angular.extend({}, credential); + }, + createCredential: function (credential) { + for(var i = 0; i < _encryptedFields.length; i++){ + var field = _encryptedFields[i]; + var fieldValue = angular.copy(credential[field]); + credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue)); + } + + var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials'); + return $http.post(queryUrl, credential).then(function (response) { + if(response.data){ + return response.data; + } else { + return response; + } + }); + }, + updateCredential: function (credential) { + for(var i = 0; i < _encryptedFields.length; i++){ + var field = _encryptedFields[i]; + var fieldValue = angular.copy(credential[field]); + credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue)); + } + + var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials/'+ credential.credential_id); + return $http.post(queryUrl, credential).then(function (response) { + if(response.data){ + return response.data; + } else { + return response; + } + }); + } + } + }]); diff --git a/js/app/services/encryptservice.js b/js/app/services/encryptservice.js new file mode 100644 index 00000000..b1e53881 --- /dev/null +++ b/js/app/services/encryptservice.js @@ -0,0 +1,44 @@ +'use strict'; + +/** + * @ngdoc service + * @name passmanApp.VaultService + * @description + * # VaultService + * Service in the passmanApp. + */ +angular.module('passmanApp') + .service('EncryptService', ['VaultService', function (VaultService) { + // AngularJS will instantiate a singleton by calling "new" on this function + var encryption_config = { + adata:"", + iter: 1000, + ks: 256, + mode: 'ccm', + ts:64 + }; + + return { + encryptString: function(string){ + var _key = VaultService.getActiveVault().vaultKey; + var rp = { + + }; + var ct = sjcl.encrypt(_key, string, encryption_config, rp); + return window.btoa(ct); + }, + decryptString: function(ciphertext){ + ciphertext = window.atob(ciphertext); + var _key = VaultService.getActiveVault().vaultKey; + var rp = { + + }; + try { + return sjcl.decrypt(_key, ciphertext, encryption_config, rp) + } catch(e) { + Error("Can't decrypt: "+e); + return; + } + } + } + }]); diff --git a/js/app/services/vaultservice.js b/js/app/services/vaultservice.js index c4d0aecf..dbb3b931 100644 --- a/js/app/services/vaultservice.js +++ b/js/app/services/vaultservice.js @@ -10,9 +10,10 @@ angular.module('passmanApp') .service('VaultService', ['$http', 'CacheService', function ($http, CacheService) { // AngularJS will instantiate a singleton by calling "new" on this function + var _activeVault; return { getVaults: function(){ - var queryUrl = OC.generateUrl('apps/passman/api/v1/vaults'); + var queryUrl = OC.generateUrl('apps/passman/api/v2/vaults'); return $http.get(queryUrl).then(function (response) { if(response.data){ return response.data; @@ -21,8 +22,14 @@ angular.module('passmanApp') } }); }, + setActiveVault: function(vault){ + _activeVault = vault; + }, + getActiveVault: function(vault){ + return _activeVault; + }, createVault: function (vaultName) { - var queryUrl = OC.generateUrl('apps/passman/api/v1/vaults'); + var queryUrl = OC.generateUrl('apps/passman/api/v2/vaults'); return $http.post(queryUrl, { vault_name: vaultName }).then(function (response) { if(response.data){ return response.data; @@ -32,7 +39,7 @@ angular.module('passmanApp') }); }, getVault: function (vault) { - var queryUrl = OC.generateUrl('apps/passman/api/v1/vaults/' + vault.vault_id); + var queryUrl = OC.generateUrl('apps/passman/api/v2/vaults/' + vault.vault_id); return $http.get(queryUrl).then(function (response) { if(response.data){ return response.data; @@ -42,7 +49,7 @@ angular.module('passmanApp') }); }, updateVault: function (vault) { - var queryUrl = OC.generateUrl('apps/passman/api/v1/vaults/' + vault.vault_id); + var queryUrl = OC.generateUrl('apps/passman/api/v2/vaults/' + vault.vault_id); return $http.post(queryUrl).then(function (response) { if(response.data){ return response.data; @@ -52,7 +59,7 @@ angular.module('passmanApp') }); }, deleteVault: function (vault) { - var queryUrl = OC.generateUrl('apps/passman/api/v1/vaults/' + vault.vault_id); + var queryUrl = OC.generateUrl('apps/passman/api/v2/vaults/' + vault.vault_id); return $http.delete(queryUrl).then(function (response) { if(response.data){ return response.data; diff --git a/js/script.js b/js/script.js deleted file mode 100644 index e69de29b..00000000 diff --git a/js/templates.js b/js/templates.js index ec8d3f6a..2ce42972 100644 --- a/js/templates.js +++ b/js/templates.js @@ -3,5 +3,5 @@ angular.module('templates-main', ['views/vaults.html']); angular.module('views/vaults.html', []).run(['$templateCache', function($templateCache) { 'use strict'; $templateCache.put('views/vaults.html', - '
  • + Create a new vault
  • {{vault.name}}
    Created: {{vault.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}} | Last accessed: {{vault.last_access * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}
  • No vaults found, why not create one?
  • Go back to vaults
'); + '
  • + Create a new vault
  • {{vault.name}}
    Created: {{vault.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}} | Last accessed: {{vault.last_access * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}} Never
  • No vaults found, why not create one?
  • Go back to vaults
'); }]); diff --git a/js/vendor/sjcl/sjcl.js b/js/vendor/sjcl/sjcl.js new file mode 100644 index 00000000..56783fa1 --- /dev/null +++ b/js/vendor/sjcl/sjcl.js @@ -0,0 +1,41 @@ +"use strict";var sjcl={cipher:{},hash:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}}; +sjcl.cipher.aes=function(a){this.h[0][0][0]||this.z();var b,c,d,e,f=this.h[0][4],g=this.h[1];b=a.length;var h=1;if(b!==4&&b!==6&&b!==8)throw new sjcl.exception.invalid("invalid aes key size");this.a=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(a%b===0||b===8&&a%b===4){c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255];if(a%b===0){c=c<<8^c>>>24^h<<24;h=h<<1^(h>>7)*283}}d[a]=d[a-b]^c}for(b=0;a;b++,a--){c=d[b&3?a:a-4];e[b]=a<=4||b<4?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^ +g[3][f[c&255]]}}; +sjcl.cipher.aes.prototype={encrypt:function(a){return this.I(a,0)},decrypt:function(a){return this.I(a,1)},h:[[[],[],[],[],[]],[[],[],[],[],[]]],z:function(){var a=this.h[0],b=this.h[1],c=a[4],d=b[4],e,f,g,h=[],i=[],k,j,l,m;for(e=0;e<0x100;e++)i[(h[e]=e<<1^(e>>7)*283)^e]=e;for(f=g=0;!c[f];f^=k||1,g=i[g]||1){l=g^g<<1^g<<2^g<<3^g<<4;l=l>>8^l&255^99;c[f]=l;d[l]=f;j=h[e=h[k=h[f]]];m=j*0x1010101^e*0x10001^k*0x101^f*0x1010100;j=h[l]*0x101^l*0x1010100;for(e=0;e<4;e++){a[e][f]=j=j<<24^j>>>8;b[e][l]=m=m<<24^m>>>8}}for(e= + 0;e<5;e++){a[e]=a[e].slice(0);b[e]=b[e].slice(0)}},I:function(a,b){if(a.length!==4)throw new sjcl.exception.invalid("invalid aes block size");var c=this.a[b],d=a[0]^c[0],e=a[b?3:1]^c[1],f=a[2]^c[2];a=a[b?1:3]^c[3];var g,h,i,k=c.length/4-2,j,l=4,m=[0,0,0,0];g=this.h[b];var n=g[0],o=g[1],p=g[2],q=g[3],r=g[4];for(j=0;j>>24]^o[e>>16&255]^p[f>>8&255]^q[a&255]^c[l];h=n[e>>>24]^o[f>>16&255]^p[a>>8&255]^q[d&255]^c[l+1];i=n[f>>>24]^o[a>>16&255]^p[d>>8&255]^q[e&255]^c[l+2];a=n[a>>>24]^o[d>>16& + 255]^p[e>>8&255]^q[f&255]^c[l+3];l+=4;d=g;e=h;f=i}for(j=0;j<4;j++){m[b?3&-j:j]=r[d>>>24]<<24^r[e>>16&255]<<16^r[f>>8&255]<<8^r[a&255]^c[l++];g=d;d=e;e=f;f=a;a=g}return m}}; +sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.P(a.slice(b/32),32-(b&31)).slice(1);return c===undefined?a:sjcl.bitArray.clamp(a,c-b)},concat:function(a,b){if(a.length===0||b.length===0)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return d===32?a.concat(b):sjcl.bitArray.P(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;if(b===0)return 0;return(b-1)*32+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(a.length*320&&b)a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1);return a},partial:function(a,b,c){if(a===32)return b;return(c?b|0:b<<32-a)+a*0x10000000000},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return false;var c=0,d;for(d=0;d=32;b-=32){d.push(c);c=0}if(b===0)return d.concat(a); + for(e=0;e>>b);c=a[e]<<32-b}e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,b+a>32?c:d.pop(),1));return d},k:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]}}; +sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d>>24);e<<=8}return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c>>e)>>>26);if(e<6){g=a[d]<<6-e;e+=26;d++}else{g<<=6;e-=6}}for(;c.length&3&&!b;)c+="=";return c},toBits:function(a){a=a.replace(/\s|=/g,"");var b=[],c,d=0,e=sjcl.codec.base64.F,f=0,g;for(c=0;c26){d-=26;b.push(f^g>>>d);f=g<<32-d}else{d+=6;f^=g<<32-d}}d&56&&b.push(sjcl.bitArray.partial(d&56,f,1));return b}};sjcl.hash.sha256=function(a){this.a[0]||this.z();if(a){this.n=a.n.slice(0);this.i=a.i.slice(0);this.e=a.e}else this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()}; +sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.n=this.N.slice(0);this.i=[];this.e=0;return this},update:function(a){if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);var b,c=this.i=sjcl.bitArray.concat(this.i,a);b=this.e;a=this.e=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)this.D(c.splice(0,16));return this},finalize:function(){var a,b=this.i,c=this.n;b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.e/ + 4294967296));for(b.push(this.e|0);b.length;)this.D(b.splice(0,16));this.reset();return c},N:[],a:[],z:function(){function a(e){return(e-Math.floor(e))*0x100000000|0}var b=0,c=2,d;a:for(;b<64;c++){for(d=2;d*d<=c;d++)if(c%d===0)continue a;if(b<8)this.N[b]=a(Math.pow(c,0.5));this.a[b]=a(Math.pow(c,1/3));b++}},D:function(a){var b,c,d=a.slice(0),e=this.n,f=this.a,g=e[0],h=e[1],i=e[2],k=e[3],j=e[4],l=e[5],m=e[6],n=e[7];for(a=0;a<64;a++){if(a<16)b=d[a];else{b=d[a+1&15];c=d[a+14&15];b=d[a&15]=(b>>>7^b>>>18^ + b>>>3^b<<25^b<<14)+(c>>>17^c>>>19^c>>>10^c<<15^c<<13)+d[a&15]+d[a+9&15]|0}b=b+n+(j>>>6^j>>>11^j>>>25^j<<26^j<<21^j<<7)+(m^j&(l^m))+f[a];n=m;m=l;l=j;j=k+b|0;k=i;i=h;h=g;g=b+(h&i^k&(h^i))+(h>>>2^h>>>13^h>>>22^h<<30^h<<19^h<<10)|0}e[0]=e[0]+g|0;e[1]=e[1]+h|0;e[2]=e[2]+i|0;e[3]=e[3]+k|0;e[4]=e[4]+j|0;e[5]=e[5]+l|0;e[6]=e[6]+m|0;e[7]=e[7]+n|0}}; +sjcl.mode.ccm={name:"ccm",encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,i=h.bitLength(c)/8,k=h.bitLength(g)/8;e=e||64;d=d||[];if(i<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(f=2;f<4&&k>>>8*f;f++);if(f<15-i)f=15-i;c=h.clamp(c,8*(15-f));b=sjcl.mode.ccm.H(a,b,c,d,e,f);g=sjcl.mode.ccm.J(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),i=f.clamp(b,h-e),k=f.bitSlice(b, + h-e);h=(h-e)/8;if(g<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;b<4&&h>>>8*b;b++);if(b<15-g)b=15-g;c=f.clamp(c,8*(15-b));i=sjcl.mode.ccm.J(a,i,c,k,e,b);a=sjcl.mode.ccm.H(a,i.data,c,d,e,b);if(!f.equal(i.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match");return i.data},H:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,i=h.k;e/=8;if(e%2||e<4||e>16)throw new sjcl.exception.invalid("ccm: invalid tag length");if(d.length>0xffffffff||b.length>0xffffffff)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data"); + f=[h.partial(8,(d.length?64:0)|e-2<<2|f-1)];f=h.concat(f,c);f[3]|=h.bitLength(b)/8;f=a.encrypt(f);if(d.length){c=h.bitLength(d)/8;if(c<=65279)g=[h.partial(16,c)];else if(c<=0xffffffff)g=h.concat([h.partial(16,65534)],[c]);g=h.concat(g,d);for(d=0;d>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^(a[0]>>>31)*135]}};sjcl.misc.hmac=function(a,b){this.M=b=b||sjcl.hash.sha256;var c=[[],[]],d=b.prototype.blockSize/32;this.l=[new b,new b];if(a.length>d)a=b.hash(a);for(b=0;b0;){b++;e>>>=1}this.b[g].update([d,this.u++,2,b,f,a.length].concat(a))}break;case "string":if(b=== + undefined)b=a.length;this.b[g].update([d,this.u++,3,b,f,a.length]);this.b[g].update(a);break;default:i=1}if(i)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");this.j[g]+=b;this.f+=b;if(h===0){this.isReady()!==0&&this.K("seeded",Math.max(this.g,this.f));this.K("progress",this.getProgress())}},isReady:function(a){a=this.C[a!==undefined?a:this.t];return this.g&&this.g>=a?this.j[0]>80&&(new Date).valueOf()>this.O?3:1:this.f>=a?2:0},getProgress:function(a){a= + this.C[a?a:this.t];return this.g>=a?1:this.f>a?1:this.f/a},startCollectors:function(){if(!this.m){if(window.addEventListener){window.addEventListener("load",this.o,false);window.addEventListener("mousemove",this.p,false)}else if(document.attachEvent){document.attachEvent("onload",this.o);document.attachEvent("onmousemove",this.p)}else throw new sjcl.exception.bug("can't attach event");this.m=true}},stopCollectors:function(){if(this.m){if(window.removeEventListener){window.removeEventListener("load", + this.o,false);window.removeEventListener("mousemove",this.p,false)}else if(window.detachEvent){window.detachEvent("onload",this.o);window.detachEvent("onmousemove",this.p)}this.m=false}},addEventListener:function(a,b){this.r[a][this.Q++]=b},removeEventListener:function(a,b){var c;a=this.r[a];var d=[];for(c in a)a.hasOwnProperty(c)&&a[c]===b&&d.push(c);for(b=0;b=1<this.g)this.g=c;this.A++;this.T(b)},p:function(a){sjcl.random.addEntropy([a.x||a.clientX||a.offsetX||0,a.y||a.clientY||a.offsetY||0],2,"mouse")},o:function(){sjcl.random.addEntropy((new Date).valueOf(),2,"loadtime")},K:function(a,b){var c;a=sjcl.random.r[a]; + var d=[];for(c in a)a.hasOwnProperty(c)&&d.push(a[c]);for(c=0;c4)throw new sjcl.exception.invalid("json encrypt: invalid parameters");if(typeof a==="string"){g=sjcl.misc.cachedPbkdf2(a,f);a=g.key.slice(0,f.ks/32);f.salt=g.salt}if(typeof b==="string")b=sjcl.codec.utf8String.toBits(b);if(typeof c==="string")c=sjcl.codec.utf8String.toBits(c);g=new sjcl.cipher[f.cipher](a);e.c(d,f);d.key=a;f.ct=sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return e.encode(f)},decrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.c(e.c(e.c({},e.defaults),e.decode(b)), + c,true);var f;c=b.adata;if(typeof b.salt==="string")b.salt=sjcl.codec.base64.toBits(b.salt);if(typeof b.iv==="string")b.iv=sjcl.codec.base64.toBits(b.iv);if(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||typeof a==="string"&&b.iter<=100||b.ts!==64&&b.ts!==96&&b.ts!==128||b.ks!==128&&b.ks!==192&&b.ks!==0x100||!b.iv||b.iv.length<2||b.iv.length>4)throw new sjcl.exception.invalid("json decrypt: invalid parameters");if(typeof a==="string"){f=sjcl.misc.cachedPbkdf2(a,b);a=f.key.slice(0,b.ks/32);b.salt=f.salt}if(typeof c=== + "string")c=sjcl.codec.utf8String.toBits(c);f=new sjcl.cipher[b.cipher](a);c=sjcl.mode[b.mode].decrypt(f,b.ct,b.iv,c,b.ts);e.c(d,b);d.key=a;return sjcl.codec.utf8String.fromBits(c)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b)){if(!b.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");c+=d+'"'+b+'":';d=",";switch(typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';break;case "object":c+='"'+ + sjcl.codec.base64.fromBits(a[b],1)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type");}}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");if(!a.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;cconnection = $connection; +/** + * @method integer getId() + * @method void setId(integer $value) + * @method void setGuid(string $value) + * @method string getGuid() + * @method void setVaultId(integer $value) + * @method integer getVaultId() + * @method void setUserId(string $value) + * @method string getUserId() + * @method void setLabel(string $value) + * @method string getLabel() + * @method void setDescription(string $value) + * @method string getDescription() + * @method void setCreated(string $value) + * @method string getCreated() + * @method void setChanged(string $value) + * @method string getChanged() + * @method void setTags(string $value) + * @method string getTags() + * @method void setEmail(string $value) + * @method string getEmail() + * @method void setUsername(string $value) + * @method string getUsername() + * @method void setPassword(string $value) + * @method string getPassword() + * @method void setUrl(string $value) + * @method string getUrl() + * @method void setFavicon(string $value) + * @method string getFavicon() + * @method void setRenewInterval(integer $value) + * @method integer getRenewInterval() + * @method void setExpireTime(integer $value) + * @method integer getExpireTime() + * @method void setDeleteTime(integer $value) + * @method integer getDeleteTime() + * @method void setFiles(string $value) + * @method string getFiles() + * @method void setCustomFields(string $value) + * @method string getCustomFields() + * @method void setOtp(string $value) + * @method string getOtp() + * @method void setHidden(bool $value) + * @method string getHidden() + + + + */ + + +class Credential extends Entity implements \JsonSerializable{ + + use EntityJSONSerializer; + + protected $guid; + protected $vaultId; + protected $userId; + protected $label; + protected $description; + protected $created; + protected $changed; + protected $tags; + protected $email; + protected $username; + protected $password; + protected $url; + protected $favicon; + protected $renewInterval; + protected $expireTime; + protected $deleteTime; + protected $files; + protected $customFields; + protected $otp; + protected $hidden; + + public function __construct() { + // add types in constructor + $this->addType('created', 'integer'); + $this->addType('changed', 'integer'); + $this->addType('renewInterval', 'integer'); + $this->addType('expireTime', 'integer'); + $this->addType('deleteTime', 'integer'); } + + /** + * Turns entitie attributes into an array + */ + public function jsonSerialize() { + return [ + 'credential_id' => $this->getId(), + 'guid' => $this->getGuid(), + 'user_id' => $this->getUserId(), + 'vault_id' => $this->getVaultId(), + 'label' => $this->getLabel(), + 'description' => $this->getDescription(), + 'created' => $this->getCreated(), + 'changed' => $this->getChanged(), + 'tags' => $this->getTags(), + 'email' => $this->getEmail(), + 'username' => $this->getUsername(), + 'password' => $this->getPassword(), + 'url' => $this->getUrl(), + 'favicon' => $this->getFavicon(), + 'renew_interval' => $this->getRenewInterval(), + 'expire_time' => $this->getExpireTime(), + 'delete_time' => $this->getDeleteTime(), + 'files' => $this->getFiles(), + 'custom_fields' => $this->getCustomFields(), + 'otp' => $this->getOtp(), + 'hidden' => $this->getHidden(), + ]; + } } \ No newline at end of file diff --git a/lib/Db/CredentialMapper.php b/lib/Db/CredentialMapper.php new file mode 100644 index 00000000..74b889b2 --- /dev/null +++ b/lib/Db/CredentialMapper.php @@ -0,0 +1,68 @@ + + * @copyright Sander Brand 2016 + */ +namespace OCA\Passman\Db; + +use OCA\Passman\Utility\Utils; +use OCP\IDBConnection; +use OCP\AppFramework\Db\Mapper; + +class CredentialMapper extends Mapper { + private $utils; + public function __construct(IDBConnection $db, Utils $utils) { + parent::__construct($db, 'passman_credentials'); + $this->utils = $utils; + } + + + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + */ + public function find($vault_id) { + $sql = 'SELECT * FROM `*PREFIX*passman_vaults` ' . + 'WHERE `user_id` = ? LIMIT 1'; + return $this->findEntity($sql, [$vault_id]); + } + + public function findVaultsFromUser($userId){ + $sql = 'SELECT id, name, created, guid, last_access FROM `*PREFIX*passman_vaults` ' . + 'WHERE `user_id` = ? '; + $params = [$userId]; + return $this->findEntities($sql, $params); + } + + public function create($raw_credential){ + $credential = new Credential(); + + $credential->setGuid($this->utils->GUID()); + $credential->setVaultId($raw_credential['vault_id']); + $credential->setUserId($raw_credential['user_id']); + $credential->setLabel($raw_credential['label']); + $credential->setDescription($raw_credential['description']); + $credential->setCreated($this->utils->getTime()); + $credential->setChanged($this->utils->getTime()); + $credential->setTags($raw_credential['tags']); + $credential->setEmail($raw_credential['email']); + $credential->setUsername($raw_credential['username']); + $credential->setPassword($raw_credential['password']); + $credential->setUrl($raw_credential['url']); + $credential->setFavicon($raw_credential['favicon']); + $credential->setRenewInterval($raw_credential['renew_interval']); + $credential->setExpireTime($raw_credential['expire_time']); + $credential->setDeleteTime($raw_credential['delete_time']); + $credential->setFiles($raw_credential['files']); + $credential->setCustomFields($raw_credential['custom_fields']); + $credential->setOtp($raw_credential['otp']); + $credential->setHidden($raw_credential['hidden']); + return parent::insert($credential); + } + +} \ No newline at end of file diff --git a/lib/Db/Vault.php b/lib/Db/Vault.php index d7e02636..5da6e6f4 100644 --- a/lib/Db/Vault.php +++ b/lib/Db/Vault.php @@ -43,23 +43,12 @@ class Vault extends Entity implements \JsonSerializable{ $this->addType('created', 'integer'); $this->addType('lastAccess', 'integer'); } - - public static function fromRow($row){ - $vault = new Vault(); - $vault->setId($row['id']); - $vault->setGuid($row['guid']); - $vault->setName($row['name']); - $vault->setCreated($row['created']); - $vault->setlastAccess($row['last_access']); - return $vault; - } - /** * Turns entitie attributes into an array */ public function jsonSerialize() { return [ - 'id' => $this->getId(), + 'vault_id' => $this->getId(), 'guid' => $this->getGuid(), 'name' => $this->getName(), 'created' => $this->getCreated(), diff --git a/lib/Service/CredentialService.php b/lib/Service/CredentialService.php new file mode 100644 index 00000000..143d43e0 --- /dev/null +++ b/lib/Service/CredentialService.php @@ -0,0 +1,31 @@ + + * @copyright Sander Brand 2016 + */ + +namespace OCA\Passman\Service; + +use OCP\IConfig; +use OCP\AppFramework\Db\DoesNotExistException; + +use OCA\Passman\Db\CredentialMapper; + + +class CredentialService { + + private $credentialMapper; + + public function __construct(CredentialMapper $credentialMapper) { + $this->credentialMapper = $credentialMapper; + } + + public function createCredential($credential) { + return $this->credentialMapper->create($credential); + } +} \ No newline at end of file diff --git a/sass/vaults.scss b/sass/vaults.scss index c083d259..a53b8cf6 100644 --- a/sass/vaults.scss +++ b/sass/vaults.scss @@ -45,4 +45,10 @@ margin-top: 10px; } } +} + +@media screen and (max-width: 768px){ + .vault_wrapper{ + width: 90%; + } } \ No newline at end of file diff --git a/templates/main.php b/templates/main.php index 3c6ae883..a342fa78 100644 --- a/templates/main.php +++ b/templates/main.php @@ -10,6 +10,7 @@ script('passman', 'vendor/angular-route/angular-route.min'); 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/sjcl/sjcl'); script('passman', 'app/app'); @@ -21,6 +22,7 @@ script('passman', 'app/services/cacheservice'); script('passman', 'app/services/vaultservice'); script('passman', 'app/services/credentialservice'); script('passman', 'app/services/settingsservice'); +script('passman', 'app/services/encryptservice'); /* diff --git a/templates/views/vaults.html b/templates/views/vaults.html index fd5c796e..04b46491 100644 --- a/templates/views/vaults.html +++ b/templates/views/vaults.html @@ -13,7 +13,9 @@ Created: {{vault.created * 1000 | date:'dd-MM-yyyy @ HH:mm:ss'}} | - Last accessed: {{vault.last_access * 1000 | date:'dd-MM-yyyy @ HH:mm:ss'}} + Last accessed: + {{vault.last_access * 1000 | date:'dd-MM-yyyy @ HH:mm:ss'}} + Never @@ -30,6 +32,14 @@
+
+ Vault password + +
+
+ Repeat vault password + +
Create vault @@ -51,7 +61,7 @@ Please input the password for {{list_selected_vault.name}}
- +
@@ -67,7 +77,7 @@
-
+
Decrypt vault