UNPKG

nitrogen-core

Version:

Core services used across ingestion, registry, and consumption servers.

362 lines (285 loc) 12.4 kB
var async = require('async') , core = require('../../lib') , fs = require('fs') , moment = require('moment') , mongoose = require('mongoose') , path = require('path') , revalidator = require('revalidator') , flat = require('flat'); var buildVisibility = function(message, callback) { // if the creator has already marked this as public, shortcircuit. // if (message.public) return callback(null, message); // find all permissions for from: principal core.services.permissions.permissionsForCached(message.from, function(err, permissions) { if (err) return callback(err); //message.public = false; var authorizedHash = {}; authorizedHash[message.from] = true; if (message.to) authorizedHash[message.to] = true; // iterate through permissions in priority order. // track specific or all-principals authorizations. permissions.forEach(function(permission) { if (!permission.action || permission.action === 'subscribe') { if (permission.issued_to) { if (!authorizedHash[permission.issued_to] && permission.authorized) authorizedHash[permission.issued_to] = true; } // else { // message.public = true; //} } }); // message.visible_to = message.public ? [] : Object.keys(authorizedHash); message.visible_to = Object.keys(authorizedHash); core.log.debug('final message visibility: ' + message.visible_to); return callback(null, message); }); }; var checkFrom = function(principal, message, callback) { if (!message.from || message.from.equals(principal.id)) return callback(null, new core.models.Permission({ authorized: true })); core.services.permissions.authorize({ principal: principal.id, principal_for: message.from, action: 'admin' }, message, callback); }; /* var count = function(query, callback) { core.models.Message.count(query, callback); }; */ var createInternal = function(principal, msg, callback) { var message = translate(msg); delete message.created_at; message._id = new mongoose.Types.ObjectId(); if (!message.from) message.from = principal.id; validate(message, function(err, fromPrincipal, toPrincipal) { if (err) return callback(err); checkFrom(principal, message, function(err, permission) { if (err) return callback(err); if (!permission.authorized) { core.log.warn('principal: ' + principal.id + ' attempted to send message with from: of another principal: ' + JSON.stringify(message)); return callback(core.utils.authorizationError()); } var toPrincipalId = toPrincipal ? toPrincipal.id : undefined; core.services.permissions.authorize({ principal: principal.id, principal_for: toPrincipalId, action: 'send' }, message, function(err, permission) { if (err) return callback(err); if (!permission.authorized) { core.log.warn('principal: ' + principal.id + ' attempted unauthorized send of message: ' + JSON.stringify(message)); return callback(core.utils.authorizationError()); } buildVisibility(message, function(err, message) { if (err) return callback(err); message.id = new mongoose.Types.ObjectId(); message.body_length = JSON.stringify(message.body).length; core.services.subscriptions.publish('message', message, function(err) { if (err) core.log.error('message service create: publish error: ' + JSON.stringify(err)); }); async.each(core.config.archive_providers, function(archiveProvider, providerCallback) { archiveProvider.archive(message, providerCallback); }, function(err) { if (err) core.log.error('services.messages.createInternal error archiving message: ' + err); return callback(err, [message]); }); }); }); }); }); }; var create = function(principal, message, callback) { if (!message.to) return createInternal(principal, message, callback); core.services.principals.findByIdCached(core.services.principals.servicePrincipal, message.to, function(err, toPrincipal) { if (err) return callback(err); if (!toPrincipal) return callback(core.utils.badRequestError('Principal in to: field (' + message.to +') of message not found.')); if (toPrincipal.type !== "group") return createInternal(principal, message, callback); async.concat(toPrincipal.members, function(groupMember, memberCallback) { var groupMessage = new core.models.Message(message); groupMessage.to = groupMember; createInternal(principal, groupMessage, memberCallback); }, callback); }); }; var createMany = function(principal, messages, callback) { var ts = new Date(); if (!messages) return callback(null, []); async.concat(messages, function(message, cb) { if (!message.ts) { message.ts = ts; // increment the timestamp of the next message (if any) by 1ms to preserve ordering in message stream. ts = new Date(ts.getTime() + 1); } create(principal, message, cb); }, callback); }; var find = function(principal, filter, options, callback) { return core.config.primary_archive_provider.find(principal, filter, options, callback); }; /* var findById = function(principal, messageId, callback) { core.models.Message.findOne(core.services.principals.filterForPrincipal(principal, { "_id": messageId }), function(err, message) { if (err) return callback(err); return callback(null, message); }); }; */ var initialize = function(callback) { if (core.config.validate_schemas) { loadSchemaAndClients('./'); } return callback(); }; var schemas = {}; var clients = { 'nitrogen-min.js': "", 'nitrogen.js': "" }; var loadClientPlugin = function(fullPath, clientFile) { var clientPath = path.join(fullPath, clientFile); // don't add dependencies of dependencies if (fullPath.split('node_modules').length > 2) return; if (clientFile === 'nitrogen.js' || clientFile === 'nitrogen-min.js') { core.log.info('adding client plugin: ' + clientFile + ' from: ' + fullPath); clients[clientFile] += fs.readFileSync(clientPath); } }; var loadSchema = function(fullPath, schemaFile) { var schemaPath = path.join(fullPath, schemaFile); if (schemaPath.indexOf('.json') !== -1 || schemaPath.indexOf('.js') !== -1) return; core.log.info('messages: loading schema: ' + schemaFile + ' from: ' + schemaPath); var schemaText = fs.readFileSync(schemaPath); schemas[schemaFile] = JSON.parse(schemaText); }; var processDirectoryItem = function(itemPath, item) { var fullPath = path.join(itemPath, item); var stats = fs.statSync(fullPath); // ignore files if (stats.isDirectory()) { // if the directory's name is schemas, we parse all of the contents as schemas. if (item === 'schemas') { core.log.info('parsing schemas in path: ' + fullPath); var schemas = fs.readdirSync(fullPath); schemas.forEach(function(schemaFile) { loadSchema(fullPath, schemaFile); }); } else if (item === 'browser') { var clients = fs.readdirSync(fullPath); clients.forEach(function(clientFile) { loadClientPlugin(fullPath, clientFile); }); } else { loadSchemaAndClients(fullPath); } } }; var loadSchemaAndClients = function(path) { var items = fs.readdirSync(path); items.forEach(function(item) { if (item !== 'newrelic') processDirectoryItem(path, item); }); }; var remove = function(principal, filter, callback) { async.each(core.config.archive_providers, function(archiveProvider, providerCallback) { archiveProvider.remove(principal, filter, providerCallback); }, function(err) { return callback(err); }); }; var removeLinkedResources = function(message, callback) { // the vast majority of messages will have no link and will immediately callback. if (!message.link) return callback(); core.log.info("message service: removing linked resources with link: " + message.link); core.services.blobs.remove(core.services.principals.servicePrincipal, { link: message.link }, callback); }; /* var removeOne = function(principal, message, callback) { if (!principal || !principal.is('service')) return callback(core.utils.authorizationError()); removeLinkedResources(message, function(err) { if (err) return callback(err); message.remove(callback); }); }; */ var translate = function(message) { if (!message.index_until) { message.index_until = moment().add(core.config.default_message_indexed_lifetime, 'days').toDate(); } if (message.index_until === 'forever') { message.index_until = core.models.Message.INDEX_FOREVER; } if (!message.expires || message.expires === 'never') { message.expires = core.models.Message.NEVER_EXPIRE; } if (message.to === 'service') { message.to = core.services.principals.servicePrincipal.id; } // convert json/object to model if needed if (!core.models.Message.isPrototypeOf(message)) message = new core.models.Message(message); return message; }; var flatten = function(message, opt) { var options = { safe : true, delimiter: '__' }; for (var attrname in opt) { options[attrname] = opt[attrname]; } return flat.flatten(message, options); }; var validate = function(message, callback) { if (!message.from) return callback(core.utils.badRequestError('Message must have a from principal.')); if (!message.type) return callback(core.utils.badRequestError('Message must have a message type.')); var validationFunction = core.config.validate_schemas ? validateSchema : core.utils.nop; validationFunction(message, function(err, result) { if (err) return callback(err); if (!result.valid) return callback(result.errors); core.services.principals.findByIdCached(core.services.principals.servicePrincipal, message.from, function(err, fromPrincipal) { if (err) return callback(err); if (!fromPrincipal) return callback(core.utils.badRequestError('From: principal on message was not found.')); if (!message.to) return callback(null, fromPrincipal, null); core.services.principals.findByIdCached(core.services.principals.servicePrincipal, message.to, function(err, toPrincipal) { if (err) return callback(err); if (!toPrincipal) return callback(core.utils.badRequestError('Principal in to: field (' + message.to +') of message not found.')); callback(null, fromPrincipal, toPrincipal); }); }); }); }; var validateSchema = function(message, callback) { if (message.isCustomType() || !(message.type in schemas)) return callback(null, { valid: true }); var results = revalidator.validate(message.body, schemas[message.type]); if (!results.valid) { core.log.warn("message validation failed with errors: " + JSON.stringify(results.errors)); core.log.warn("message: " + message); } callback(null, results); }; var validateAll = function(messages, callback) { async.every(messages, validate, callback); }; module.exports = { clients: clients, /* count: count, */ create: create, createMany: createMany, find: find, /* findById: findById, */ initialize: initialize, remove: remove, /* removeOne: removeOne, */ translate: translate, flatten: flatten, validate: validate, validateAll: validateAll };