diff --git a/lang/js/.babelrc b/lang/js/.babelrc new file mode 100644 index 00000000..9d8d5165 --- /dev/null +++ b/lang/js/.babelrc @@ -0,0 +1 @@ +{ "presets": ["es2015"] } diff --git a/lang/js/package.json b/lang/js/package.json index 2b7dd7ee..a794188a 100644 --- a/lang/js/package.json +++ b/lang/js/package.json @@ -5,13 +5,15 @@ "main": "src/index.js", "private": true, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "mocha" }, "keywords": [], "author": "", "license": "", "devDependencies": { "webpack": "^4.5.0", - "webpack-cli": "^2.0.14" + "webpack-cli": "^2.0.14", + "chai": "^4.1.2", + "mocha": "^5.1.1" } } diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 5b092ab0..a10f9d9a 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -83,7 +83,7 @@ export class Connection{ */ post(message){ if (!this.isConnected){ - return Promise.reject(gpgme_error('CONN_NO_CONNECT')); + return Promise.reject(gpgme_error('CONN_DISCONNECTED')); } if (!message || !message instanceof GPGME_Message){ return Promise.reject(gpgme_error('PARAM_WRONG'), message); diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 2f53aa89..d26aab18 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -55,14 +55,9 @@ const err_list = { msg: 'The Message is empty.', type: 'error' }, - 'MSG_OP_PENDING': { - msg: 'There is no operation specified yet. The parameter cannot' - + ' be set', - type: 'warning' - }, 'MSG_WRONG_OP': { msg: 'The operation requested could not be found', - type: 'warning' + type: 'error' }, 'MSG_NO_KEYS' : { msg: 'There were no valid keys provided.', @@ -78,7 +73,7 @@ const err_list = { }, // generic 'PARAM_WRONG':{ - msg: 'invalid parameter was found', + msg: 'Invalid parameter was found', type: 'error' }, 'PARAM_IGNORED': { @@ -111,7 +106,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ return new GPGME_Error(code); } if (err_list[code].type === 'warning'){ - console.log(new GPGME_Error(code)); + console.warn(code + ': ' + err_list[code].msg); } return null; } else if (code === 'GNUPG_ERROR'){ diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index 841c0eda..9a69f851 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -18,6 +18,7 @@ * SPDX-License-Identifier: LGPL-2.1+ */ import { gpgme_error } from "./Errors"; +import { GPGME_Key } from "./Key"; /** * Tries to return an array of fingerprints, either from input fingerprints or @@ -26,7 +27,7 @@ import { gpgme_error } from "./Errors"; * @returns {Array} Array of fingerprints. */ -export function toKeyIdArray(input, nocheck){ +export function toKeyIdArray(input){ if (!input){ gpgme_error('MSG_NO_KEYS'); return []; @@ -46,7 +47,7 @@ export function toKeyIdArray(input, nocheck){ let fpr = ''; if (input[i] instanceof GPGME_Key){ fpr = input[i].fingerprint; - } else if (input[i].hasOwnProperty(primaryKey) && + } else if (input[i].hasOwnProperty('primaryKey') && input[i].primaryKey.hasOwnProperty(getFingerprint)){ fpr = input[i].primaryKey.getFingerprint(); } @@ -92,7 +93,7 @@ export function isFingerprint(string){ /** * check if the input is a valid Hex string with a length of 16 */ -function isLongId(string){ +export function isLongId(string){ return hextest(string, 16); }; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index f6fa7ae3..0b44b245 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -26,9 +26,9 @@ * */ -import {isFingerprint} from './Helpers' -import {gpgme_error} from './Errors' -import { GPGME_Message } from './Message'; +import { isFingerprint } from './Helpers' +import { gpgme_error } from './Errors' +import { createMessage } from './Message'; import { permittedOperations } from './permittedOperations'; export class GPGME_Key { @@ -39,7 +39,7 @@ export class GPGME_Key { set fingerprint(fpr){ if (isFingerprint(fpr) === true && !this._fingerprint){ - this._fingerprint = fingerprint; + this._fingerprint = fpr; } } @@ -181,9 +181,12 @@ function checkKey(fingerprint, property){ } return new Promise(function(resolve, reject){ if (!isFingerprint(fingerprint)){ - reject('KEY_INVALID'); + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage ('keyinfo'); + if (msg instanceof Error){ + reject(gpgme_error('PARAM_WRONG')); } - let msg = new GPGME_Message('keyinfo'); msg.setParameter('fingerprint', this.fingerprint); return (this.connection.post(msg)).then(function(result){ if (result.hasOwnProperty(property)){ diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index e1f0a50f..470eeeec 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -18,7 +18,7 @@ * SPDX-License-Identifier: LGPL-2.1+ */ -import {GPGME_Message} from './Message' +import {createMessage} from './Message' import {GPGME_Key} from './Key' import { isFingerprint, isLongId } from './Helpers'; import { gpgme_error } from './Errors'; @@ -50,7 +50,10 @@ export class GPGME_Keyring { * */ getKeys(pattern, include_secret){ - let msg = new GPGME_Message('listkeys'); + let msg = createMessage('listkeys'); + if (msg instanceof Error){ + return Promise.reject(msg); + } if (pattern && typeof(pattern) === 'string'){ msg.setParameter('pattern', pattern); } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 06ac8db2..4d242277 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -19,13 +19,34 @@ */ import { permittedOperations } from './permittedOperations' import { gpgme_error } from './Errors' -export class GPGME_Message { + +export function createMessage(operation){ + if (typeof(operation) !== 'string'){ + return gpgme_error('PARAM_WRONG'); + } + if (permittedOperations.hasOwnProperty(operation)){ + return new GPGME_Message(operation); + } else { + return gpgme_error('MSG_WRONG_OP'); + } +} + +/** + * Prepares a communication request. It checks operations and parameters in + * ./permittedOperations. + * @param {String} operation + */ +class GPGME_Message { //TODO getter constructor(operation){ - setOperation(this, operation); + this.operation = operation; } + set operation (op){ + + + } get operation(){ return this._msg.op; } @@ -41,9 +62,6 @@ export class GPGME_Message { if (!param || typeof(param) !== 'string'){ return gpgme_error('PARAM_WRONG'); } - if (!this._msg || !this._msg.op){ - return gpgme_error('MSG_OP_PENDING'); - } let po = permittedOperations[this._msg.op]; if (!po){ return gpgme_error('MSG_WRONG_OP'); @@ -90,22 +108,3 @@ export class GPGME_Message { } } - -/** - * Defines the operation this message will have - * @param {String} operation Must be defined in permittedOperations - * TODO: move to constructor? - */ -function setOperation (scope, operation){ - if (!operation || typeof(operation) !== 'string'){ - return gpgme_error('PARAM_WRONG'); - } - if (permittedOperations.hasOwnProperty(operation)){ - if (!scope._msg){ - scope._msg = {}; - } - scope._msg.op = operation; - } else { - return gpgme_error('MSG_WRONG_OP'); - } -} \ No newline at end of file diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index b504a457..2ddf2964 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -19,7 +19,7 @@ */ import {Connection} from "./Connection" -import {GPGME_Message} from './Message' +import {GPGME_Message, createMessage} from './Message' import {toKeyIdArray} from "./Helpers" import { gpgme_error } from "./Errors" import { GPGME_Keyring } from "./Keyring"; @@ -71,8 +71,10 @@ export class GpgME { */ encrypt(data, publicKeys, wildcard=false){ - let msg = new GPGME_Message('encrypt'); - + let msg = createMessage('encrypt'); + if (msg instanceof Error){ + return Promise.reject(msg) + } // TODO temporary msg.setParameter('armor', true); msg.setParameter('always-trust', true); @@ -101,7 +103,10 @@ export class GpgME { if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } - let msg = new GPGME_Message('decrypt'); + let msg = createMessage('decrypt'); + if (msg instanceof Error){ + return Promise.reject(msg); + } putData(msg, data); return this.connection.post(msg); @@ -109,21 +114,27 @@ export class GpgME { deleteKey(key, delete_secret = false, no_confirm = false){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); - let msg = new GPGME_Message('deletekey'); + let msg = createMessage('deletekey'); + if (msg instanceof Error){ + return Promise.reject(msg); + } let key_arr = toKeyIdArray(key); if (key_arr.length !== 1){ - throw('TODO'); - //should always be ONE key + return Promise.reject( + gpgme_error('GENERIC_ERROR')); + // TBD should always be ONE key? } msg.setParameter('key', key_arr[0]); if (delete_secret === true){ - msg.setParameter('allow_secret', true); //TBD + msg.setParameter('allow_secret', true); + // TBD } if (no_confirm === true){ //TODO: Do we want this hidden deep in the code? - msg.setParameter('delete_force', true); //TBD + msg.setParameter('delete_force', true); + // TBD } this.connection.post(msg).then(function(success){ - //TODO: it seems that there is always errors coming back: + // TODO: it seems that there is always errors coming back: }, function(error){ switch (error.msg){ case 'ERR_NO_ERROR': diff --git a/lang/js/test/Helpers.js b/lang/js/test/Helpers.js new file mode 100644 index 00000000..590f9f65 --- /dev/null +++ b/lang/js/test/Helpers.js @@ -0,0 +1,110 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + * SPDX-License-Identifier: LGPL-2.1+ + */ + +import { expect } from "../node_modules/chai/chai"; +import { gpgme_error} from "../src/Errors"; +import { GPGME_Key } from "../src/Key"; +import { isLongId, isFingerprint, toKeyIdArray } from "../src/Helpers" + +const helper_params = { + validLongId: '0A0A0A0A0A0A0A0A', + validGPGME_Key: new GPGME_Key('ADDBC303B6D31026F5EB4591A27EABDF283121BB'), + validKeys: [new GPGME_Key('A1E3BC45BDC8E87B74F4392D53B151A1368E50F3'), + 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', + new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')], + validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + invalidLongId: '9A9A7A7A8A9A9A7A7A8A', + invalidFingerprint: [{hello:'World'}], + invalidKeyArray: {curiosity:'uncat'}, + invalidKeyArray_OneBad: [ + new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), + 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', + '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], + invalidErrorCode: 'Please type in all your passwords.' +} + +describe('Error Object handling', function(){ + it('check the Timeout error', function(){ + let test0 = gpgme_error('CONN_TIMEOUT'); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('CONN_TIMEOUT'); + }); + it('Error Object returns generic code if code is not listed', function(){ + let test0 = gpgme_error(helper_params.invalidErrorCode); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('GENERIC_ERROR'); + }); + + it('Warnings like PARAM_IGNORED should not return errors', function(){ + let test0 = gpgme_error('PARAM_IGNORED'); + expect(test0).to.be.null; + }); +}); + +describe('Fingerprint checking', function(){ + it('isFingerprint(): valid Fingerprint', function(){ + let test0 = isFingerprint(helper_params.validFingerprint); + expect(test0).to.be.true; + }); + it('isFingerprint(): invalid Fingerprint', function(){ + let test0 = isFingerprint(helper_params.invalidFingerprint); + expect(test0).to.be.false; + }); +}); +describe('Converting to Fingerprint', function(){ + it('Correct Inputs', function(){ + it('Fingerprint string', function(){ + let test0 = toKeyIdArray(helper_params.validFingerprint); + expect(test0).to.be.an('array'); + expect(test0).to.include(helper_params.validFingerprint); + }); + it('GPGME_Key', function(){ + expect(helper_params.validGPGME_Key).to.be.an.instanceof(GPGME_Key); + let test0 = toKeyIdArray(helper_params.validGPGME_Key); + expect(test0).to.be.an('array'); + expect(test0).to.include(helper_params.validGPGME_Key.fingerprint); + }); + it('Array of valid inputs', function(){ + let test0 = toKeyIdArray(helper_params.validKeys); + expect(test0).to.be.an('array'); + expect(test0).to.have.lengthOf(helper_params.validKeys.length); + }); + }); + describe('Incorrect inputs', function(){ + it('valid Long ID', function(){ + let test0 = toKeyIdArray(helper_params.validLongId); + expect(test0).to.be.empty; + }); + it('invalidFingerprint', function(){ + let test0 = toKeyIdArray(helper_params.invalidFingerprint); + expect(test0).to.be.empty; + }); + it('invalidKeyArray', function(){ + let test0 = toKeyIdArray(helper_params.invalidKeyArray); + expect(test0).to.be.empty; + }); + it('Partially invalid array', function(){ + let test0 = toKeyIdArray(helper_params.invalidKeyArray_OneBad); + expect(test0).to.be.an('array'); + expect(test0).to.have.lengthOf( + helper_params.invalidKeyArray_OneBad.length - 1); + }); + }); +}); diff --git a/lang/js/test/Message.js b/lang/js/test/Message.js new file mode 100644 index 00000000..454b8ca3 --- /dev/null +++ b/lang/js/test/Message.js @@ -0,0 +1,42 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + * SPDX-License-Identifier: LGPL-2.1+ + */ + +import { expect } from "../node_modules/chai/chai"; +import { GPGME_Message, createMessage } from "../src/Message"; + +const message_params = { + invalid_op_action : 'dance', + invalid_op_type : [234, 34, '<>'], +} + +describe('Message Object', function(){ + describe('incorrect initialization', function(){ + it('non-allowed operation', function(){ + let test0 = createMessage(message_params.invalid_op_action); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('MSG_WRONG_OP'); + }); + it('wrong parameter type in constructor', function(){ + let test0 = createMessage(message_params.invalid_op_type); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('PARAM_WRONG'); + }); + }); +}); diff --git a/lang/js/test/mocha.opts b/lang/js/test/mocha.opts new file mode 100644 index 00000000..65adc1c3 --- /dev/null +++ b/lang/js/test/mocha.opts @@ -0,0 +1,4 @@ +--require babel-register +--reporter spec +--ui bdd +--colors