diff options
-rw-r--r-- | share/www/script/test/replicator_db.js | 129 | ||||
-rw-r--r-- | src/couchdb/couch_js_functions.hrl | 76 |
2 files changed, 201 insertions, 4 deletions
diff --git a/share/www/script/test/replicator_db.js b/share/www/script/test/replicator_db.js index 95a196d7..e8986d6e 100644 --- a/share/www/script/test/replicator_db.js +++ b/share/www/script/test/replicator_db.js @@ -916,6 +916,131 @@ couchTests.replicator_db = function(debug) { } + function rep_doc_field_validation() { + var docs = makeDocs(1, 5); + + populate_db(dbA, docs); + populate_db(dbB, []); + + var repDoc = { + _id: "rep1", + target: dbB.name + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because source field is missing"); + } catch (x) { + TEquals("forbidden", x.error); + } + + repDoc = { + _id: "rep1", + source: 123, + target: dbB.name + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because source field is a number"); + } catch (x) { + TEquals("forbidden", x.error); + } + + repDoc = { + _id: "rep1", + source: dbA.name + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because target field is missing"); + } catch (x) { + TEquals("forbidden", x.error); + } + + repDoc = { + _id: "rep1", + source: dbA.name, + target: null + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because target field is null"); + } catch (x) { + TEquals("forbidden", x.error); + } + + repDoc = { + _id: "rep1", + source: dbA.name, + target: { url: 123 } + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because target.url field is not a string"); + } catch (x) { + TEquals("forbidden", x.error); + } + + repDoc = { + _id: "rep1", + source: dbA.name, + target: { url: dbB.name, auth: null } + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because target.auth field is null"); + } catch (x) { + TEquals("forbidden", x.error); + } + + repDoc = { + _id: "rep1", + source: dbA.name, + target: { url: dbB.name, auth: "foo:bar" } + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because target.auth field is not an object"); + } catch (x) { + TEquals("forbidden", x.error); + } + + repDoc = { + _id: "rep1", + source: dbA.name, + target: dbB.name, + continuous: "true" + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because continuous is not a boolean"); + } catch (x) { + TEquals("forbidden", x.error); + } + + repDoc = { + _id: "rep1", + source: dbA.name, + target: dbB.name, + filter: 123 + }; + + try { + repDb.save(repDoc); + T(false, "should have failed because filter is not a string"); + } catch (x) { + TEquals("forbidden", x.error); + } + } + + // run all the tests var server_config = [ { @@ -983,6 +1108,10 @@ couchTests.replicator_db = function(debug) { restartServer(); run_on_modified_server(server_config, compact_rep_db); + repDb.deleteDb(); + restartServer(); + run_on_modified_server(server_config, rep_doc_field_validation); + /* * Disabled, since error state would be set on the document only after * the exponential backoff retry done by the replicator database listener diff --git a/src/couchdb/couch_js_functions.hrl b/src/couchdb/couch_js_functions.hrl index 67f06686..31d5b598 100644 --- a/src/couchdb/couch_js_functions.hrl +++ b/src/couchdb/couch_js_functions.hrl @@ -101,32 +101,100 @@ function(newDoc, oldDoc, userCtx) { function reportError(error_msg) { log('Error writing document `' + newDoc._id + - '` to replicator DB: ' + error_msg); + '\\' to the replicator database: ' + error_msg); throw({forbidden: error_msg}); } + function validateEndpoint(endpoint, fieldName) { + if ((typeof endpoint !== 'string') && + ((typeof endpoint !== 'object') || (endpoint === null))) { + + reportError('The `' + fieldName + '\\' property must exist' + + ' and be either a string or an object.'); + } + + if (typeof endpoint === 'object') { + if ((typeof endpoint.url !== 'string') || !endpoint.url) { + reportError('The url property must exist in the `' + + fieldName + '\\' field and must be a non-empty string.'); + } + + if ((typeof endpoint.auth !== 'undefined') && + ((typeof endpoint.auth !== 'object') || + endpoint.auth === null)) { + + reportError('`' + fieldName + + '.auth\\' must be a non-null object.'); + } + + if ((typeof endpoint.headers !== 'undefined') && + ((typeof endpoint.headers !== 'object') || + endpoint.headers === null)) { + + reportError('`' + fieldName + + '.headers\\' must be a non-null object.'); + } + } + } + var isReplicator = (userCtx.roles.indexOf('_replicator') >= 0); if (oldDoc && !newDoc._deleted && !isReplicator) { reportError('Only the replicator can edit replication documents.'); } + if (!newDoc._deleted) { + validateEndpoint(newDoc.source, 'source'); + validateEndpoint(newDoc.target, 'target'); + + if ((typeof newDoc.create_target !== 'undefined') && + (typeof newDoc.create_target !== 'boolean')) { + + reportError('The `create_target\\' field must be a boolean.'); + } + + if ((typeof newDoc.continuous !== 'undefined') && + (typeof newDoc.continuous !== 'boolean')) { + + reportError('The `continuous\\' field must be a boolean.'); + } + + if ((typeof newDoc.doc_ids !== 'undefined') && + !isArray(newDoc.doc_ids)) { + + reportError('The `doc_ids\\' field must be an array of strings.'); + } + + if ((typeof newDoc.filter !== 'undefined') && + ((typeof newDoc.filter !== 'string') || !newDoc.filter)) { + + reportError('The `filter\\' field must be a non-empty string.'); + } + + if ((typeof newDoc.query_params !== 'undefined') && + ((typeof newDoc.query_params !== 'object') || + newDoc.query_params === null)) { + + reportError('The `query_params\\' field must be an object.'); + } + } + if (newDoc.user_ctx) { var user_ctx = newDoc.user_ctx; if (typeof user_ctx !== 'object') { - reportError('The user_ctx property must be an object.'); + reportError('The `user_ctx\\' property must be an object.'); } if (!(user_ctx.name === null || (typeof user_ctx.name === 'undefined') || ((typeof user_ctx.name === 'string') && user_ctx.name.length > 0))) { - reportError('The name property of the user_ctx must be a ' + + reportError('The `user_ctx.name\\' property must be a ' + 'non-empty string.'); } if (user_ctx.roles && !isArray(user_ctx.roles)) { - reportError('The roles property of the user_ctx must be ' + + reportError('The `user_ctx.roles\\' property must be ' + 'an array of strings.'); } |