| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 | /** * Some Notes: This is a friggin complicated piece of code. * * "scope" in this file means "where did this token come from and what is using it", so 99% of the time * the "scope" is going to be "user" because it would be a user token. This is not to be confused with * the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else. * * */const _              = require('lodash');const logger         = require('../logger').access;const validator      = require('ajv');const error          = require('./error');const userModel      = require('../models/user');const proxyHostModel = require('../models/proxy_host');const TokenModel     = require('../models/token');const roleSchema     = require('./access/roles.json');const permsSchema    = require('./access/permissions.json');module.exports = function (token_string) {	let Token                 = new TokenModel();	let token_data            = null;	let initialised           = false;	let object_cache          = {};	let allow_internal_access = false;	let user_roles            = [];	let permissions           = {};	/**	 * Loads the Token object from the token string	 *	 * @returns {Promise}	 */	this.init = () => {		return new Promise((resolve, reject) => {			if (initialised) {				resolve();			} else if (!token_string) {				reject(new error.PermissionError('Permission Denied'));			} else {				resolve(Token.load(token_string)					.then((data) => {						token_data = data;						// At this point we need to load the user from the DB and make sure they:						// - exist (and not soft deleted)						// - still have the appropriate scopes for this token						// This is only required when the User ID is supplied or if the token scope has `user`						if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) {							// Has token user id or token user scope							return userModel								.query()								.where('id', token_data.attrs.id)								.andWhere('is_deleted', 0)								.andWhere('is_disabled', 0)								.allowEager('[permissions]')								.eager('[permissions]')								.first()								.then((user) => {									if (user) {										// make sure user has all scopes of the token										// The `user` role is not added against the user row, so we have to just add it here to get past this check.										user.roles.push('user');										let is_ok = true;										_.forEach(token_data.scope, (scope_item) => {											if (_.indexOf(user.roles, scope_item) === -1) {												is_ok = false;											}										});										if (!is_ok) {											throw new error.AuthError('Invalid token scope for User');										} else {											initialised = true;											user_roles  = user.roles;											permissions = user.permissions;										}									} else {										throw new error.AuthError('User cannot be loaded for Token');									}								});						} else {							initialised = true;						}					}));			}		});	};	/**	 * Fetches the object ids from the database, only once per object type, for this token.	 * This only applies to USER token scopes, as all other tokens are not really bound	 * by object scopes	 *	 * @param   {String} object_type	 * @returns {Promise}	 */	this.loadObjects = (object_type) => {		return new Promise((resolve, reject) => {			if (Token.hasScope('user')) {				if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) {					reject(new error.AuthError('User Token supplied without a User ID'));				} else {					let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0;					let query;					if (typeof object_cache[object_type] === 'undefined') {						switch (object_type) {						// USERS - should only return yourself						case 'users':							resolve(token_user_id ? [token_user_id] : []);							break;							// Proxy Hosts						case 'proxy_hosts':							query = proxyHostModel								.query()								.select('id')								.andWhere('is_deleted', 0);							if (permissions.visibility === 'user') {								query.andWhere('owner_user_id', token_user_id);							}							resolve(query								.then((rows) => {									let result = [];									_.forEach(rows, (rule_row) => {										result.push(rule_row.id);									});									// enum should not have less than 1 item									if (!result.length) {										result.push(0);									}									return result;								})							);							break;							// DEFAULT: null						default:							resolve(null);							break;						}					} else {						resolve(object_cache[object_type]);					}				}			} else {				resolve(null);			}		})			.then((objects) => {				object_cache[object_type] = objects;				return objects;			});	};	/**	 * Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema	 *	 * @param   {String} permission_label	 * @returns {Object}	 */	this.getObjectSchema = (permission_label) => {		let base_object_type = permission_label.split(':').shift();		let schema = {			$id:                  'objects',			$schema:              'http://json-schema.org/draft-07/schema#',			description:          'Actor Properties',			type:                 'object',			additionalProperties: false,			properties:           {				user_id: {					anyOf: [						{							type: 'number',							enum: [Token.get('attrs').id]						}					]				},				scope: {					type:    'string',					pattern: '^' + Token.get('scope') + '$'				}			}		};		return this.loadObjects(base_object_type)			.then((object_result) => {				if (typeof object_result === 'object' && object_result !== null) {					schema.properties[base_object_type] = {						type:    'number',						enum:    object_result,						minimum: 1					};				} else {					schema.properties[base_object_type] = {						type:    'number',						minimum: 1					};				}				return schema;			});	};	return {		token: Token,		/**		 *		 * @param   {Boolean}  [allow_internal]		 * @returns {Promise}		 */		load: (allow_internal) => {			return new Promise(function (resolve/*, reject*/) {				if (token_string) {					resolve(Token.load(token_string));				} else {					allow_internal_access = allow_internal;					resolve(allow_internal_access || null);				}			});		},		reloadObjects: this.loadObjects,		/**		 *		 * @param {String}  permission		 * @param {*}       [data]		 * @returns {Promise}		 */		can: (permission, data) => {			if (allow_internal_access === true) {				return Promise.resolve(true);				//return true;			} else {				return this.init()					.then(() => {						// Initialised, token decoded ok						return this.getObjectSchema(permission)							.then((objectSchema) => {								let data_schema = {									[permission]: {										data:                         data,										scope:                        Token.get('scope'),										roles:                        user_roles,										permission_visibility:        permissions.visibility,										permission_proxy_hosts:       permissions.proxy_hosts,										permission_redirection_hosts: permissions.redirection_hosts,										permission_dead_hosts:        permissions.dead_hosts,										permission_streams:           permissions.streams,										permission_access_lists:      permissions.access_lists,										permission_certificates:      permissions.certificates									}								};								let permissionSchema = {									$schema:              'http://json-schema.org/draft-07/schema#',									$async:               true,									$id:                  'permissions',									additionalProperties: false,									properties:           {}								};								permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');								// logger.info('objectSchema', JSON.stringify(objectSchema, null, 2));								// logger.info('permissionSchema', JSON.stringify(permissionSchema, null, 2));								// logger.info('data_schema', JSON.stringify(data_schema, null, 2));								let ajv = validator({									verbose:      true,									allErrors:    true,									format:       'full',									missingRefs:  'fail',									breakOnError: true,									coerceTypes:  true,									schemas:      [										roleSchema,										permsSchema,										objectSchema,										permissionSchema									]								});								return ajv.validate('permissions', data_schema)									.then(() => {										return data_schema[permission];									});							});					})					.catch((err) => {						err.permission      = permission;						err.permission_data = data;						logger.error(permission, data, err.message);						throw new error.PermissionError('Permission Denied', err);					});			}		}	};};
 |