access.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /**
  2. * Some Notes: This is a friggin complicated piece of code.
  3. *
  4. * "scope" in this file means "where did this token come from and what is using it", so 99% of the time
  5. * the "scope" is going to be "user" because it would be a user token. This is not to be confused with
  6. * the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else.
  7. *
  8. *
  9. */
  10. const _ = require('lodash');
  11. const logger = require('../logger').access;
  12. const Ajv = require('ajv/dist/2020');
  13. const error = require('./error');
  14. const userModel = require('../models/user');
  15. const proxyHostModel = require('../models/proxy_host');
  16. const TokenModel = require('../models/token');
  17. const roleSchema = require('./access/roles.json');
  18. const permsSchema = require('./access/permissions.json');
  19. module.exports = function (token_string) {
  20. let Token = new TokenModel();
  21. let token_data = null;
  22. let initialised = false;
  23. let object_cache = {};
  24. let allow_internal_access = false;
  25. let user_roles = [];
  26. let permissions = {};
  27. /**
  28. * Loads the Token object from the token string
  29. *
  30. * @returns {Promise}
  31. */
  32. this.init = () => {
  33. return new Promise((resolve, reject) => {
  34. if (initialised) {
  35. resolve();
  36. } else if (!token_string) {
  37. reject(new error.PermissionError('Permission Denied'));
  38. } else {
  39. resolve(Token.load(token_string)
  40. .then((data) => {
  41. token_data = data;
  42. // At this point we need to load the user from the DB and make sure they:
  43. // - exist (and not soft deleted)
  44. // - still have the appropriate scopes for this token
  45. // This is only required when the User ID is supplied or if the token scope has `user`
  46. if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) {
  47. // Has token user id or token user scope
  48. return userModel
  49. .query()
  50. .where('id', token_data.attrs.id)
  51. .andWhere('is_deleted', 0)
  52. .andWhere('is_disabled', 0)
  53. .allowGraph('[permissions]')
  54. .withGraphFetched('[permissions]')
  55. .first()
  56. .then((user) => {
  57. if (user) {
  58. // make sure user has all scopes of the token
  59. // The `user` role is not added against the user row, so we have to just add it here to get past this check.
  60. user.roles.push('user');
  61. let is_ok = true;
  62. _.forEach(token_data.scope, (scope_item) => {
  63. if (_.indexOf(user.roles, scope_item) === -1) {
  64. is_ok = false;
  65. }
  66. });
  67. if (!is_ok) {
  68. throw new error.AuthError('Invalid token scope for User');
  69. } else {
  70. initialised = true;
  71. user_roles = user.roles;
  72. permissions = user.permissions;
  73. }
  74. } else {
  75. throw new error.AuthError('User cannot be loaded for Token');
  76. }
  77. });
  78. } else {
  79. initialised = true;
  80. }
  81. }));
  82. }
  83. });
  84. };
  85. /**
  86. * Fetches the object ids from the database, only once per object type, for this token.
  87. * This only applies to USER token scopes, as all other tokens are not really bound
  88. * by object scopes
  89. *
  90. * @param {String} object_type
  91. * @returns {Promise}
  92. */
  93. this.loadObjects = (object_type) => {
  94. return new Promise((resolve, reject) => {
  95. if (Token.hasScope('user')) {
  96. if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) {
  97. reject(new error.AuthError('User Token supplied without a User ID'));
  98. } else {
  99. let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0;
  100. let query;
  101. if (typeof object_cache[object_type] === 'undefined') {
  102. switch (object_type) {
  103. // USERS - should only return yourself
  104. case 'users':
  105. resolve(token_user_id ? [token_user_id] : []);
  106. break;
  107. // Proxy Hosts
  108. case 'proxy_hosts':
  109. query = proxyHostModel
  110. .query()
  111. .select('id')
  112. .andWhere('is_deleted', 0);
  113. if (permissions.visibility === 'user') {
  114. query.andWhere('owner_user_id', token_user_id);
  115. }
  116. resolve(query
  117. .then((rows) => {
  118. let result = [];
  119. _.forEach(rows, (rule_row) => {
  120. result.push(rule_row.id);
  121. });
  122. // enum should not have less than 1 item
  123. if (!result.length) {
  124. result.push(0);
  125. }
  126. return result;
  127. })
  128. );
  129. break;
  130. // DEFAULT: null
  131. default:
  132. resolve(null);
  133. break;
  134. }
  135. } else {
  136. resolve(object_cache[object_type]);
  137. }
  138. }
  139. } else {
  140. resolve(null);
  141. }
  142. })
  143. .then((objects) => {
  144. object_cache[object_type] = objects;
  145. return objects;
  146. });
  147. };
  148. /**
  149. * Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema
  150. *
  151. * @param {String} permission_label
  152. * @returns {Object}
  153. */
  154. this.getObjectSchema = (permission_label) => {
  155. let base_object_type = permission_label.split(':').shift();
  156. let schema = {
  157. $id: 'objects',
  158. description: 'Actor Properties',
  159. type: 'object',
  160. additionalProperties: false,
  161. properties: {
  162. user_id: {
  163. anyOf: [
  164. {
  165. type: 'number',
  166. enum: [Token.get('attrs').id]
  167. }
  168. ]
  169. },
  170. scope: {
  171. type: 'string',
  172. pattern: '^' + Token.get('scope') + '$'
  173. }
  174. }
  175. };
  176. return this.loadObjects(base_object_type)
  177. .then((object_result) => {
  178. if (typeof object_result === 'object' && object_result !== null) {
  179. schema.properties[base_object_type] = {
  180. type: 'number',
  181. enum: object_result,
  182. minimum: 1
  183. };
  184. } else {
  185. schema.properties[base_object_type] = {
  186. type: 'number',
  187. minimum: 1
  188. };
  189. }
  190. return schema;
  191. });
  192. };
  193. return {
  194. token: Token,
  195. /**
  196. *
  197. * @param {Boolean} [allow_internal]
  198. * @returns {Promise}
  199. */
  200. load: (allow_internal) => {
  201. return new Promise(function (resolve/*, reject*/) {
  202. if (token_string) {
  203. resolve(Token.load(token_string));
  204. } else {
  205. allow_internal_access = allow_internal;
  206. resolve(allow_internal_access || null);
  207. }
  208. });
  209. },
  210. reloadObjects: this.loadObjects,
  211. /**
  212. *
  213. * @param {String} permission
  214. * @param {*} [data]
  215. * @returns {Promise}
  216. */
  217. can: (permission, data) => {
  218. if (allow_internal_access === true) {
  219. return Promise.resolve(true);
  220. //return true;
  221. } else {
  222. return this.init()
  223. .then(() => {
  224. // Initialised, token decoded ok
  225. return this.getObjectSchema(permission)
  226. .then((objectSchema) => {
  227. const data_schema = {
  228. [permission]: {
  229. data: data,
  230. scope: Token.get('scope'),
  231. roles: user_roles,
  232. permission_visibility: permissions.visibility,
  233. permission_proxy_hosts: permissions.proxy_hosts,
  234. permission_redirection_hosts: permissions.redirection_hosts,
  235. permission_dead_hosts: permissions.dead_hosts,
  236. permission_streams: permissions.streams,
  237. permission_access_lists: permissions.access_lists,
  238. permission_certificates: permissions.certificates
  239. }
  240. };
  241. let permissionSchema = {
  242. $async: true,
  243. $id: 'permissions',
  244. additionalProperties: false,
  245. properties: {}
  246. };
  247. permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');
  248. const ajv = new Ajv({
  249. verbose: true,
  250. allErrors: true,
  251. missingRefs: 'fail',
  252. breakOnError: true,
  253. coerceTypes: true,
  254. schemas: [
  255. roleSchema,
  256. permsSchema,
  257. objectSchema,
  258. permissionSchema
  259. ]
  260. });
  261. return ajv.validate('permissions', data_schema)
  262. .then(() => {
  263. return data_schema[permission];
  264. });
  265. });
  266. })
  267. .catch((err) => {
  268. err.permission = permission;
  269. err.permission_data = data;
  270. logger.error(permission, data, err.message);
  271. throw new error.PermissionError('Permission Denied', err);
  272. });
  273. }
  274. }
  275. };
  276. };