diff --git a/src/groups.js b/src/groups.js index 80d34429d..c079e715a 100644 --- a/src/groups.js +++ b/src/groups.js @@ -23,6 +23,7 @@ var assert = require('assert'), constants = require('./constants.js'), DatabaseError = require('./databaseerror.js'), groupdb = require('./groupdb.js'), + mailboxdb = require('./mailboxdb.js'), util = require('util'); // http://dustinsenos.com/articles/customErrorsInNode @@ -77,11 +78,16 @@ function create(name, callback) { var error = validateGroupname(name); if (error) return callback(error); - groupdb.add(name /* id */, name, function (error) { + mailboxdb.add(name, name /* owner */, mailboxdb.TYPE_GROUP, function (error) { if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new GroupError(GroupError.ALREADY_EXISTS)); if (error) return callback(new GroupError(GroupError.INTERNAL_ERROR, error)); - callback(null, { id: name, name: name }); + groupdb.add(name /* id */, name, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new GroupError(GroupError.ALREADY_EXISTS)); + if (error) return callback(new GroupError(GroupError.INTERNAL_ERROR, error)); + + callback(null, { id: name, name: name }); + }); }); } @@ -92,11 +98,16 @@ function remove(id, callback) { // never allow admin group to be deleted if (id === constants.ADMIN_GROUP_ID) return callback(new GroupError(GroupError.NOT_ALLOWED)); - groupdb.del(id, function (error) { + mailboxdb.delByOwnerId(id, function (error) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new GroupError(GroupError.NOT_FOUND)); if (error) return callback(new GroupError(GroupError.INTERNAL_ERROR, error)); - callback(null); + groupdb.del(id, function (error) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new GroupError(GroupError.NOT_FOUND)); + if (error) return callback(new GroupError(GroupError.INTERNAL_ERROR, error)); + + callback(null); + }); }); } diff --git a/src/mailboxdb.js b/src/mailboxdb.js index decd809d7..2cd696d8d 100644 --- a/src/mailboxdb.js +++ b/src/mailboxdb.js @@ -7,8 +7,13 @@ exports = module.exports = { getAll: getAll, getAliases: getAliases, setAliases: setAliases, + delByOwnerId: delByOwnerId, - _clear: clear + _clear: clear, + + TYPE_USER: 'user', + TYPE_APP: 'app', + TYPE_GROUP: 'group' }; var assert = require('assert'), @@ -18,11 +23,13 @@ var assert = require('assert'), var MAILBOX_FIELDS = [ 'name', 'ownerId', 'ownerType', 'aliasTarget', 'creationTime' ].join(','); -function add(name, callback) { +function add(name, ownerId, ownerType, callback) { assert.strictEqual(typeof name, 'string'); + assert.strictEqual(typeof ownerId, 'string'); + assert.strictEqual(typeof ownerType, 'string'); assert.strictEqual(typeof callback, 'function'); - database.query('INSERT INTO mailboxes (name) VALUES (?)', [ name ], function (error) { + database.query('INSERT INTO mailboxes (name, ownerId, ownerType) VALUES (?, ?, ?)', [ name, ownerId, ownerType ], function (error) { if (error && error.code === 'ER_DUP_ENTRY') return callback(new DatabaseError(DatabaseError.ALREADY_EXISTS)); if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); @@ -52,6 +59,19 @@ function del(name, callback) { }); } +function delByOwnerId(id, callback) { + assert.strictEqual(typeof id, 'string'); + assert.strictEqual(typeof callback, 'function'); + + // deletes aliases as well + database.query('DELETE FROM mailboxes WHERE ownerId=?', [ id ], function (error, result) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + if (result.affectedRows === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); + + callback(null); + }); +} + function postProcess(result) { result.aliases = result.aliases ? result.aliases.split(',') : [ ]; } @@ -94,16 +114,18 @@ function getAll(callback) { }); } -function setAliases(name, aliases, callback) { +function setAliases(name, aliases, ownerId, ownerType, callback) { assert.strictEqual(typeof name, 'string'); assert(util.isArray(aliases)); + assert.strictEqual(typeof ownerId, 'string'); + assert.strictEqual(typeof ownerType, 'string'); assert.strictEqual(typeof callback, 'function'); // also cleanup the groupMembers table var queries = []; queries.push({ query: 'DELETE FROM mailboxes WHERE aliasTarget = ?', args: [ name ] }); aliases.forEach(function (alias) { - queries.push({ query: 'INSERT INTO mailboxes (name, aliasTarget) VALUES (?, ?)', args: [ alias, name ] }); + queries.push({ query: 'INSERT INTO mailboxes (name, aliasTarget, ownerId, ownerType) VALUES (?, ?, ?, ?)', args: [ alias, name, ownerId, ownerType ] }); }); database.transaction(queries, function (error) { diff --git a/src/mailboxes.js b/src/mailboxes.js index 8516e2a5d..e87861bcc 100644 --- a/src/mailboxes.js +++ b/src/mailboxes.js @@ -8,16 +8,12 @@ exports = module.exports = { setAliases: setAliases, getAliases: getAliases, - setupAliases: setupAliases, - MailboxError: MailboxError }; var assert = require('assert'), - async = require('async'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:mailboxes'), - docker = require('./docker.js'), mailboxdb = require('./mailboxdb.js'), util = require('util'); @@ -85,35 +81,17 @@ function add(name, callback) { }); } -function pushAlias(name, aliases, callback) { - if (process.env.BOX_ENV === 'test') return callback(); - - var cmd = [ '/addons/mail/service.sh', 'set-alias', name ].concat(aliases); - - debug('pushing alias for %s : %j', name, aliases); - - docker.execContainer('mail', cmd, { }, function (error) { - if (error) return callback(new MailboxError(MailboxError.EXTERNAL_ERROR, error)); - - callback(); - }); -} - function del(name, callback) { assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof callback, 'function'); - pushAlias(name, [ ], function (error) { - if (error) return callback(error); + mailboxdb.del(name, function (error) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailboxError(MailboxError.NOT_FOUND)); + if (error) return callback(new MailboxError(MailboxError.INTERNAL_ERROR, error)); - mailboxdb.del(name, function (error) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailboxError(MailboxError.NOT_FOUND)); - if (error) return callback(new MailboxError(MailboxError.INTERNAL_ERROR, error)); + debug('deleted mailbox %s', name); - debug('deleted mailbox %s', name); - - callback(); - }); + callback(); }); } @@ -151,17 +129,13 @@ function setAliases(name, aliases, callback) { if (error) return callback(error); } - pushAlias(name, aliases, function (error) { - if (error) return callback(error); - - mailboxdb.setAliases(name, aliases, function (error) { - if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new MailboxError(MailboxError.ALREADY_EXISTS, error.message)); - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailboxError(MailboxError.NOT_FOUND)); - if (error) return callback(new MailboxError(MailboxError.INTERNAL_ERROR, error)); + mailboxdb.setAliases(name, aliases, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new MailboxError(MailboxError.ALREADY_EXISTS, error.message)); + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailboxError(MailboxError.NOT_FOUND)); + if (error) return callback(new MailboxError(MailboxError.INTERNAL_ERROR, error)); - callback(null); - }); + callback(null); }); } @@ -176,23 +150,3 @@ function getAliases(name, callback) { callback(null, aliases); }); } - -// push aliases to the mail container on startup -function setupAliases(callback) { - assert.strictEqual(typeof callback, 'function'); - - getAll(function (error, mailboxes) { - if (error) return callback(error); - - async.each(mailboxes, function (mailbox, iteratorDone) { - getAliases(mailbox.name, function (error, aliases) { - if (error) return iteratorDone(error); - - if (aliases.length === 0) return iteratorDone(); - - pushAlias(mailbox.name, aliases, iteratorDone); - }); - }, callback); - }); -} - diff --git a/src/platform.js b/src/platform.js index 0a4ecdba1..9d4474989 100644 --- a/src/platform.js +++ b/src/platform.js @@ -19,7 +19,6 @@ var apps = require('./apps.js'), fs = require('fs'), hat = require('hat'), infra = require('./infra_version.js'), - mailboxes = require('./mailboxes.js'), paths = require('./paths.js'), safe = require('safetydance'), settings = require('./settings.js'), @@ -262,22 +261,18 @@ function startMail(callback) { shell.execSync('startMail', cmd); - mailboxes.setupAliases(function (error) { - if (error) return callback(error); + if (!mailConfig.enabled || process.env.BOX_ENV === 'test') return callback(); - if (!mailConfig.enabled || process.env.BOX_ENV === 'test') return callback(); + // Add MX and DMARC record. Note that DMARC policy depends on DKIM signing and thus works + // only if we use our internal mail server. + var records = [ + { subdomain: '_dmarc', type: 'TXT', values: [ '"v=DMARC1; p=reject; pct=100"' ] }, + { subdomain: '', type: 'MX', values: [ '10 ' + config.mailFqdn() + '.' ] } + ]; - // Add MX and DMARC record. Note that DMARC policy depends on DKIM signing and thus works - // only if we use our internal mail server. - var records = [ - { subdomain: '_dmarc', type: 'TXT', values: [ '"v=DMARC1; p=reject; pct=100"' ] }, - { subdomain: '', type: 'MX', values: [ '10 ' + config.mailFqdn() + '.' ] } - ]; - - async.mapSeries(records, function (record, iteratorCallback) { - subdomains.upsert(record.subdomain, record.type, record.values, iteratorCallback); - }, callback); - }); + async.mapSeries(records, function (record, iteratorCallback) { + subdomains.upsert(record.subdomain, record.type, record.values, iteratorCallback); + }, callback); }); }); } diff --git a/src/test/database-test.js b/src/test/database-test.js index 8ab5ac51f..d66e932d9 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -1181,14 +1181,14 @@ describe('database', function () { describe('mailboxes', function () { it('add succeeds', function (done) { - mailboxdb.add('support', function (error, mailbox) { + mailboxdb.add('support', 'osticket', mailboxdb.TYPE_APP, function (error, mailbox) { expect(error).to.be(null); done(); }); }); it('cannot add dup entry', function (done) { - mailboxdb.add('support', function (error, mailbox) { + mailboxdb.add('support', 'support', mailboxdb.TYPE_USER, function (error, mailbox) { expect(error.reason).to.be(DatabaseError.ALREADY_EXISTS); done(); }); @@ -1216,27 +1216,27 @@ describe('database', function () { }); it('can set alias', function (done) { - mailboxdb.setAliases('support2', [ 'support2', 'help' ], function (error) { + mailboxdb.setAliases('support', [ 'support2', 'help' ], 'support', 'user', function (error) { expect(error).to.be(null); done(); }); }); it('can get alias', function (done) { - mailboxdb.getAliases('support2', function (error, results) { + mailboxdb.getAliases('support', function (error, results) { expect(error).to.be(null); expect(results.length).to.be(2); expect(results[0]).to.be('help'); - expect(results[1]).to.be('support2') + expect(results[1]).to.be('support2'); done(); }); }); it('unset aliases', function (done) { - mailboxdb.setAliases('support2', [ ], function (error) { + mailboxdb.setAliases('support', [ ], 'support', 'user', function (error) { expect(error).to.be(null); - mailboxdb.getAliases('support2', function (error, results) { + mailboxdb.getAliases('support', function (error, results) { expect(error).to.be(null); expect(results.length).to.be(0); done(); @@ -1252,4 +1252,3 @@ describe('database', function () { }); }); }); - diff --git a/src/user.js b/src/user.js index 487b5ed68..eeb9e2abf 100644 --- a/src/user.js +++ b/src/user.js @@ -35,7 +35,7 @@ var assert = require('assert'), GroupError = groups.GroupError, hat = require('hat'), mailer = require('./mailer.js'), - mailboxes = require('./mailboxes.js'), + mailboxdb = require('./mailboxdb.js'), safe = require('safetydance'), tokendb = require('./tokendb.js'), userdb = require('./userdb.js'), @@ -51,6 +51,12 @@ var CRYPTO_KEY_LENGTH = 512; // bits var NOOP_CALLBACK = function (error) { if (error) debug(error); }; +function asyncIf(cond, func, next) { + if (!cond) return next(); + + func(next); +} + // http://dustinsenos.com/articles/customErrorsInNode // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi function UserError(reason, errorOrMessage) { @@ -176,17 +182,23 @@ function createUser(username, password, email, displayName, auditSource, options showTutorial: true }; - userdb.add(user.id, user, function (error) { + asyncIf(!!username, mailboxdb.add.bind(null, username, user.id /* owner */, mailboxdb.TYPE_USER), function (error) { + if (error) return callback(error); + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message)); if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); - eventlog.add(eventlog.ACTION_USER_ADD, auditSource, { userId: user.id, email: user.email }); - if (username) mailboxes.add(username, NOOP_CALLBACK); + userdb.add(user.id, user, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message)); + if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); - callback(null, user); + eventlog.add(eventlog.ACTION_USER_ADD, auditSource, { userId: user.id, email: user.email }); - if (!owner) mailer.userAdded(user, sendInvite); - if (sendInvite) mailer.sendInvite(user, invitor); + callback(null, user); + + if (!owner) mailer.userAdded(user, sendInvite); + if (sendInvite) mailer.sendInvite(user, invitor); + }); }); }); }); @@ -272,9 +284,8 @@ function removeUser(userId, auditSource, callback) { if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); eventlog.add(eventlog.ACTION_USER_REMOVE, auditSource, { userId: userId }); - if (user.username) mailboxes.del(user.username, NOOP_CALLBACK); - callback(null); + if (user.username) mailboxdb.delByOwnerId(user.id, callback); else callback(); mailer.userRemoved(user); }); @@ -361,16 +372,26 @@ function updateUser(userId, data, auditSource, callback) { if (error) return callback(error); } - userdb.update(userId, data, function (error) { + function doUpdate(error, callback) { if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message)); - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND, error)); if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); - eventlog.add(eventlog.ACTION_USER_UPDATE, auditSource, { userId: userId }); - if (data.username) mailboxes.add(data.username, NOOP_CALLBACK); // TODO: do this only when username actually changes + userdb.update(userId, data, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message)); + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND, error)); + if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); - callback(null); - }); + eventlog.add(eventlog.ACTION_USER_UPDATE, auditSource, { userId: userId }); + + callback(null); + }); + } + + if (data.username) { + mailboxdb.add(data.username, userId /* owner */, mailboxdb.TYPE_USER, doUpdate); // TODO: do this only when username actually changes + } else { + doUpdate(null, callback); + } } function setGroups(userId, groupIds, callback) {